Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions .github/workflows/generate-api-docs-accounts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Docs Repository Receiver - Non-Versioned Paths
# Receives trigger from openzeppelin-accounts and generates API docs and guides

name: Generate API Docs - Accounts

on:
workflow_dispatch:
inputs:
tag_name:
description: 'Tag name or commit SHA to generate docs from'
required: true
type: string
default: 'main'
trigger_type:
description: 'Trigger type (tag or commit)'
required: false
type: string
default: 'tag'

permissions:
contents: write
pull-requests: write

env:
SOURCE_REPO: 'OpenZeppelin/openzeppelin-accounts'
SOURCE_REPO_URL: 'https://github.com/OpenZeppelin/openzeppelin-accounts.git'
SOURCE_PACKAGE: 'packages/contracts'
PACKAGE_NAME: '@openzeppelin-accounts/contracts'
GUIDES_MDX: 'docs'
BASE_OUTPUT_PATH: 'content/accounts'
USE_VERSIONED_PATHS: 'false'

jobs:
generate-docs:
runs-on: ubuntu-latest

steps:
- name: Log trigger information
run: |
echo "🚀 API Documentation Generation Triggered"
echo "📦 Repository: ${{ env.SOURCE_REPO }}"
echo "🏷️ Tag/Ref: ${{ inputs.tag_name }}"
echo "📋 Trigger type: ${{ inputs.trigger_type }}"

- name: Checkout docs repository
uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup pnpm
uses: pnpm/action-setup@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Determine output directory
id: config
run: |
TAG_NAME="${{ inputs.tag_name }}"
TRIGGER_TYPE="${{ inputs.trigger_type }}"
BASE_PATH="${{ env.BASE_OUTPUT_PATH }}"
USE_VERSIONED="${{ env.USE_VERSIONED_PATHS }}"

if [[ "$USE_VERSIONED" == "false" ]]; then
OUTPUT_DIR="${BASE_PATH}/api"
echo "📁 Using non-versioned path"
elif [[ "$TRIGGER_TYPE" == "commit" ]]; then
OUTPUT_DIR="${BASE_PATH}/latest/api"
echo "🔄 Using latest path for commit-based trigger"
else
if [[ "$TAG_NAME" =~ v([0-9]+) ]]; then
MAJOR_VERSION="${BASH_REMATCH[1]}"
OUTPUT_DIR="${BASE_PATH}/${MAJOR_VERSION}.x/api"
echo "📊 Detected version: ${MAJOR_VERSION}.x"
else
OUTPUT_DIR="${BASE_PATH}/latest/api"
echo "⚠️ Non-standard tag format, using latest"
fi
fi

# Section directory holds the guides + changelog (parent of api/)
SECTION_DIR="${OUTPUT_DIR%/api}"

echo "📂 Output directory: $OUTPUT_DIR"
echo "📂 Section directory: $SECTION_DIR"
echo "output-dir=$OUTPUT_DIR" >> $GITHUB_OUTPUT
echo "section-dir=$SECTION_DIR" >> $GITHUB_OUTPUT

- name: Generate API Documentation
run: |
echo "🔄 Generating API documentation..."

node scripts/generate-api-docs.js \
--repo "${{ env.SOURCE_REPO_URL }}" \
--branch "${{ inputs.tag_name }}" \
--api-output "${{ steps.config.outputs.output-dir }}" \
--source-package "${{ env.SOURCE_PACKAGE }}" \
--package-name "${{ env.PACKAGE_NAME }}" \
--guides-output "${{ steps.config.outputs.section-dir }}" \
--guides-mdx "${{ env.GUIDES_MDX }}"

- name: Validate generated documentation
run: |
pnpm run postinstall
pnpm run lint:links

- name: Create Pull Request for documentation changes
id: create_pr
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --local user.email "docs-automation@openzeppelin.com"
git config --local user.name "OpenZeppelin Docs Bot"

if [ -n "$(git status --porcelain)" ]; then
echo "📝 Creating branch and committing documentation changes..."

BRANCH_NAME="docs/api-update-${{ env.SOURCE_REPO }}-${{ inputs.tag_name }}"
BRANCH_NAME=$(echo "$BRANCH_NAME" | sed 's/\//-/g' | sed 's/OpenZeppelin-//g')

git checkout -b "$BRANCH_NAME"
git add content/ examples/
git commit -m "Update API docs for ${{ env.SOURCE_REPO }} ${{ inputs.tag_name }}

- Repository: ${{ env.SOURCE_REPO }}
- Ref: ${{ inputs.tag_name }}
- Trigger: ${{ inputs.trigger_type }}
- Output: ${{ steps.config.outputs.output-dir }}
- Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')

Auto-generated via workflow_dispatch"

git push origin "$BRANCH_NAME"

gh pr create --title "[CI] Update API docs for ${{ env.SOURCE_REPO }} ${{ inputs.tag_name }}" \
--body "## API Documentation Update

This Pull Request updates the API documentation.

### Source Information
- **Repository:** ${{ env.SOURCE_REPO }}
- **Reference:** ${{ inputs.tag_name }}
- **Trigger Type:** ${{ inputs.trigger_type }}
- **Output Directory:** ${{ steps.config.outputs.output-dir }}
- **Timestamp:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')

Auto-generated via workflow_dispatch" \
--base main \
--head "$BRANCH_NAME" || {
echo "::error::Pull Request creation failed for ${{ env.SOURCE_REPO }} ${{ inputs.tag_name }}"
exit 1
}

echo "✅ Pull Request created successfully"
echo "pr-created=true" >> $GITHUB_OUTPUT
else
echo "ℹ️ No changes detected - documentation is up to date"
echo "pr-created=false" >> $GITHUB_OUTPUT
fi

- name: Create job summary
run: |
echo "## 📚 API Documentation Generation Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Source Information" >> $GITHUB_STEP_SUMMARY
echo "- **Repository:** ${{ env.SOURCE_REPO }}" >> $GITHUB_STEP_SUMMARY
echo "- **Reference:** ${{ inputs.tag_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Trigger Type:** ${{ inputs.trigger_type }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Output" >> $GITHUB_STEP_SUMMARY
echo "- **Directory:** ${{ steps.config.outputs.output-dir }}" >> $GITHUB_STEP_SUMMARY
echo "- **Status:** ✅ Complete" >> $GITHUB_STEP_SUMMARY
192 changes: 192 additions & 0 deletions content/accounts/api/extensions.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
---
title: "Extensions"
description: "Smart contract extensions utilities and implementations"
---

Extensions add functionality to ERC-7579 accounts.

- [`AccountERC8043Upgradeable`](#AccountERC8043Upgradeable): Extension of `AccountERC7579Upgradeable` that implements ERC-8043 init mode, installing modules and using them within a single user operation.

## Account

[`AccountERC8043Upgradeable`](#AccountERC8043Upgradeable)

<a id="AccountERC8043Upgradeable"></a>

<div style={{marginTop: "4em"}} className="w-full flex flex-row items-center justify-between">

## `AccountERC8043Upgradeable`

<a target="_blank" style={{marginTop: "1.5em"}} href="https://github.com/OpenZeppelin/openzeppelin-accounts/blob/main/packages/contracts/src/extensions/AccountERC8043Upgradeable.sol">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-github-icon lucide-github"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg>
</a>

</div>

```solidity
import "@openzeppelin-accounts/contracts/extensions/AccountERC8043Upgradeable.sol";
```

Extension of `AccountERC7579Upgradeable` that implements atomic module installation and usage
through _init mode_ following [ERC-8043](https://eip.tools/eip/8043).

This extension allows installing and using one or more modules within the same user operation.
Init mode is triggered when `userOp.initCode` starts with the 4-byte magic value `0x80438043`.
The remaining bytes of `initCode` carry the ABI-encoded module installation payload and an
EIP-712 installation signature from the account owner.

Because the `ERC-4337` EntryPoint v0.9 silently ignores `initCode` for already-deployed accounts,
and because `initCode` is included in the `userOpHash`, the module installation parameters are
both inert at the EntryPoint level and cryptographically committed to by the user operation signature.

This pattern is particularly useful for session keys and other modules that need to be
installed and immediately used, reducing user operation count from N+1 to 1.

<Callout>
Requires EntryPoint v0.9 or later.
</Callout>

<div className="bg-secondary p-4 rounded-md mb-6">
<h3 style={{ marginTop: "0"}}>Functions</h3>
<div className="font-mono">
- [_validateUserOp(userOp, userOpHash, signature)](#AccountERC8043Upgradeable-_validateUserOp-struct-PackedUserOperation-bytes32-bytes-)
<details>
<summary>AccountERC7579Upgradeable</summary>

- [__AccountERC7579_init()](#AccountERC7579Upgradeable-__AccountERC7579_init--)
- [__AccountERC7579_init_unchained()](#AccountERC7579Upgradeable-__AccountERC7579_init_unchained--)
- [fallback()](#AccountERC7579Upgradeable-fallback-bytes-)
- [accountId()](#AccountERC7579Upgradeable-accountId--)
- [supportsExecutionMode(encodedMode)](#AccountERC7579Upgradeable-supportsExecutionMode-bytes32-)
- [supportsModule(moduleTypeId)](#AccountERC7579Upgradeable-supportsModule-uint256-)
- [installModule(moduleTypeId, module, initData)](#AccountERC7579Upgradeable-installModule-uint256-address-bytes-)
- [uninstallModule(moduleTypeId, module, deInitData)](#AccountERC7579Upgradeable-uninstallModule-uint256-address-bytes-)
- [isModuleInstalled(moduleTypeId, module, additionalContext)](#AccountERC7579Upgradeable-isModuleInstalled-uint256-address-bytes-)
- [execute(mode, executionCalldata)](#AccountERC7579Upgradeable-execute-bytes32-bytes-)
- [executeFromExecutor(mode, executionCalldata)](#AccountERC7579Upgradeable-executeFromExecutor-bytes32-bytes-)
- [isValidSignature(hash, signature)](#AccountERC7579Upgradeable-isValidSignature-bytes32-bytes-)
- [_execute(mode, executionCalldata)](#AccountERC7579Upgradeable-_execute-Mode-bytes-)
- [_installModule(moduleTypeId, module, initData)](#AccountERC7579Upgradeable-_installModule-uint256-address-bytes-)
- [_uninstallModule(moduleTypeId, module, deInitData)](#AccountERC7579Upgradeable-_uninstallModule-uint256-address-bytes-)
- [_fallback()](#AccountERC7579Upgradeable-_fallback--)
- [_fallbackHandler(selector)](#AccountERC7579Upgradeable-_fallbackHandler-bytes4-)
- [_checkModule(moduleTypeId, module, additionalContext)](#AccountERC7579Upgradeable-_checkModule-uint256-address-bytes-)
- [_extractUserOpValidator(userOp)](#AccountERC7579Upgradeable-_extractUserOpValidator-struct-PackedUserOperation-)
- [_extractSignatureValidator(signature)](#AccountERC7579Upgradeable-_extractSignatureValidator-bytes-)
- [_decodeFallbackData(data)](#AccountERC7579Upgradeable-_decodeFallbackData-bytes-)
- [_rawSignatureValidation(, )](#AccountERC7579Upgradeable-_rawSignatureValidation-bytes32-bytes-)

</details>
<details>
<summary>EIP712</summary>

- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--)
- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-)
- [eip712Domain()](#EIP712-eip712Domain--)
- [_EIP712Name()](#EIP712-_EIP712Name--)
- [_EIP712Version()](#EIP712-_EIP712Version--)

</details>
<details>
<summary>Account</summary>

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

</details>
<details>
<summary>Initializable</summary>

- [_checkInitializing()](#Initializable-_checkInitializing--)
- [_disableInitializers()](#Initializable-_disableInitializers--)
- [_getInitializedVersion()](#Initializable-_getInitializedVersion--)
- [_isInitializing()](#Initializable-_isInitializing--)
- [_initializableStorageSlot()](#Initializable-_initializableStorageSlot--)

</details>
</div>
</div>

<div className="bg-secondary p-4 rounded-md mb-6">
<h3 style={{ marginTop: "0"}}>Events</h3>
<div className="font-mono">
<details>
<summary>IERC7579ModuleConfig</summary>

- [ModuleInstalled(moduleTypeId, module)](#IERC7579ModuleConfig-ModuleInstalled-uint256-address-)
- [ModuleUninstalled(moduleTypeId, module)](#IERC7579ModuleConfig-ModuleUninstalled-uint256-address-)

</details>
<details>
<summary>Initializable</summary>

- [Initialized(version)](#Initializable-Initialized-uint64-)

</details>
<details>
<summary>IERC5267</summary>

- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--)

</details>
</div>
</div>

<div className="bg-secondary p-4 rounded-md mb-6">
<h3 style={{ marginTop: "0"}}>Errors</h3>
<div className="font-mono">
<details>
<summary>AccountERC7579Upgradeable</summary>

- [ERC7579MissingFallbackHandler(selector)](#AccountERC7579Upgradeable-ERC7579MissingFallbackHandler-bytes4-)
- [ERC7579CannotDecodeFallbackData()](#AccountERC7579Upgradeable-ERC7579CannotDecodeFallbackData--)

</details>
<details>
<summary>Account</summary>

- [AccountUnauthorized(sender)](#Account-AccountUnauthorized-address-)

</details>
<details>
<summary>Initializable</summary>

- [InvalidInitialization()](#Initializable-InvalidInitialization--)
- [NotInitializing()](#Initializable-NotInitializing--)

</details>
</div>
</div>

<a id="AccountERC8043Upgradeable-_validateUserOp-struct-PackedUserOperation-bytes32-bytes-"></a>

<div className="border rounded-md mb-4">
<div className="bg-secondary flex w-full justify-between px-4">
<p className="font-bold text-sm font-mono">_validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, bytes signature) → uint256</p>
<div className="flex flex-row items-center gap-2">
<p className="font-light text-sm">internal</p>
<a className="peer" data-card href="#AccountERC8043Upgradeable-_validateUserOp-struct-PackedUserOperation-bytes32-bytes-">#</a>
</div>
</div>
<div className="px-4">

Validates a user operation, checking for the init mode magic value in `initCode`.

If `userOp.initCode` starts with `INIT_MODE_MAGIC_VALUE`, enters init mode:

1. Decodes module installation data from `userOp.initCode[4:]`
2. Validates the EIP-712 `installationSignature` over the module installation parameters
3. Installs the modules if validation succeeds
4. Continues with normal user operation validation using the forwarded `signature`
Otherwise, falls back to `super._validateUserOp`.

</div>
</div>

Loading
Loading