Skip to main content
Glama

OpenZeppelin Contracts MCP Server

Official
by OpenZeppelin
account.ts11.9 kB
import { ContractBuilder } from './contract'; import type { Contract } from './contract'; import { defineFunctions } from './utils/define-functions'; import { printContract } from './print'; import { defaults as commonDefaults, withCommonDefaults, type CommonOptions } from './common-options'; import { upgradeableName } from './options'; import { setInfo } from './set-info'; import { addSigner, signerFunctions, signers, type SignerOptions } from './signer'; import { setUpgradeableAccount } from './set-upgradeable'; export const defaults: Required<AccountOptions> = { ...commonDefaults, name: 'MyAccount', signatureValidation: 'ERC7739', ERC721Holder: true, ERC1155Holder: true, signer: 'ECDSA', batchedExecution: false, ERC7579Modules: false, } as const; export const SignatureValidationOptions = [false, 'ERC1271', 'ERC7739'] as const; export type SignatureValidationOptions = (typeof SignatureValidationOptions)[number]; export const ERC7579ModulesOptions = [false, 'AccountERC7579', 'AccountERC7579Hooked'] as const; export type ERC7579ModulesOptions = (typeof ERC7579ModulesOptions)[number]; export interface AccountOptions extends CommonOptions { name: string; signatureValidation?: SignatureValidationOptions; ERC721Holder?: boolean; ERC1155Holder?: boolean; signer?: SignerOptions; batchedExecution?: boolean; ERC7579Modules?: ERC7579ModulesOptions; } function withDefaults(opts: AccountOptions): Required<AccountOptions> { return { ...withCommonDefaults(opts), name: opts.name ?? defaults.name, signatureValidation: opts.signatureValidation ?? defaults.signatureValidation, ERC721Holder: opts.ERC721Holder ?? defaults.ERC721Holder, ERC1155Holder: opts.ERC1155Holder ?? defaults.ERC1155Holder, signer: opts.signer ?? defaults.signer, batchedExecution: opts.batchedExecution ?? defaults.batchedExecution, ERC7579Modules: opts.ERC7579Modules ?? defaults.ERC7579Modules, }; } export function printAccount(opts: AccountOptions = defaults): string { return printContract(buildAccount(opts)); } export function buildAccount(opts: AccountOptions): Contract { const allOpts = withDefaults(opts); allOpts.access = false; // Access control options are not used for Account const c = new ContractBuilder(allOpts.name); addParents(c, allOpts); overrideRawSignatureValidation(c, allOpts); setUpgradeableAccount(c, allOpts.upgradeable); setInfo(c, allOpts.info); if (opts.ERC7579Modules) { c.addImportOnly({ name: 'PackedUserOperation', path: '@openzeppelin/contracts/interfaces/draft-IERC4337.sol', transpiled: false, // PackedUserOperation doesn't start with "I" so its not recognized as an "interface object" }); } return c; } function addParents(c: ContractBuilder, opts: AccountOptions): void { // Base c.addParent({ name: 'Account', path: `@openzeppelin/contracts/account/Account.sol`, transpiled: false, }); c.addOverride({ name: 'Account', transpiled: false }, functions._validateUserOp); if (opts.signatureValidation === 'ERC7739') addEIP712(c, opts); // Extensions addSignatureValidation(c, opts); addERC7579Modules(c, opts); addSigner(c, opts.signer ?? false, opts.upgradeable ?? false); addMultisigFunctions(c, opts); addBatchedExecution(c, opts); addERC721Holder(c, opts); addERC1155Holder(c, opts); } function addSignatureValidation(c: ContractBuilder, opts: AccountOptions) { switch (opts.signatureValidation) { case 'ERC7739': c.addParent({ name: 'ERC7739', path: '@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol', transpiled: false, }); break; case 'ERC1271': c.addParent({ name: 'IERC1271', path: '@openzeppelin/contracts/interfaces/IERC1271.sol', }); c.addOverride({ name: 'IERC1271' }, functions.isValidSignature); if (!opts.ERC7579Modules) { c.setFunctionBody( [ 'return _rawSignatureValidation(hash, signature) ? IERC1271.isValidSignature.selector : bytes4(0xffffffff);', ], functions.isValidSignature, ); } break; } } function addERC721Holder(c: ContractBuilder, opts: AccountOptions): void { if (!opts.ERC721Holder) return; c.addParent({ name: 'ERC721Holder', path: '@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol', transpiled: false, }); } function addERC1155Holder(c: ContractBuilder, opts: AccountOptions): void { if (!opts.ERC1155Holder) return; c.addParent({ name: 'ERC1155Holder', path: '@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol', transpiled: false, }); } function addBatchedExecution(c: ContractBuilder, opts: AccountOptions): void { // ERC-7579 is a superset of ERC-7821 if (!opts.batchedExecution || !!opts.ERC7579Modules) return; c.addParent({ name: 'ERC7821', path: '@openzeppelin/contracts/account/extensions/draft-ERC7821.sol', transpiled: false, }); c.addOverride({ name: 'ERC7821', transpiled: false }, functions._erc7821AuthorizedExecutor); c.setFunctionBody( ['return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData);'], functions._erc7821AuthorizedExecutor, ); } function addERC7579Modules(c: ContractBuilder, opts: AccountOptions): void { if (!opts.ERC7579Modules) return; c.addParent({ name: opts.ERC7579Modules, path: `@openzeppelin/contracts/account/extensions/draft-${opts.ERC7579Modules}.sol`, }); if (opts.ERC7579Modules !== 'AccountERC7579') { c.addImportOnly({ name: 'AccountERC7579', path: '@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol', }); } // Accounts that use ERC7579 without a signer must be constructed with at least one module (executor of validation) if (!opts.signer) { c.addConstructorArgument({ type: 'uint256', name: 'moduleTypeId' }); c.addConstructorArgument({ type: 'address', name: 'module' }); c.addConstructorArgument({ type: 'bytes calldata', name: 'initData' }); c.addConstructorCode('require(moduleTypeId == MODULE_TYPE_VALIDATOR || moduleTypeId == MODULE_TYPE_EXECUTOR);'); c.addConstructorCode('_installModule(moduleTypeId, module, initData);'); } c.addOverride({ name: 'AccountERC7579' }, functions._validateUserOp); c.addOverride({ name: 'AccountERC7579' }, functions.isValidSignature); if (opts.signatureValidation === 'ERC7739') { c.addOverride({ name: 'ERC7739', transpiled: false }, functions.isValidSignature); c.setFunctionBody( [ '// ERC-7739 can return the ERC-1271 magic value, 0xffffffff (invalid) or 0x77390001 (detection).', '// If the returned value is 0xffffffff, fallback to ERC-7579 validation.', 'bytes4 erc7739magic = ERC7739.isValidSignature(hash, signature);', `return erc7739magic == bytes4(0xffffffff) ? ${opts.upgradeable ? upgradeableName('AccountERC7579') : 'AccountERC7579'}.isValidSignature(hash, signature) : erc7739magic;`, ], functions.isValidSignature, ); } } function addMultisigFunctions(c: ContractBuilder, opts: AccountOptions): void { switch (opts.signer) { case 'MultisigWeighted': c.addFunctionCode( `_setSignerWeights(${functions.setSignerWeights.args.map(({ name }) => name).join(', ')});`, functions.setSignerWeights, ); c.addModifier('onlyEntryPointOrSelf', functions.setSignerWeights); // eslint-disable-next-line no-fallthrough case 'Multisig': c.addFunctionCode(`_addSigners(${functions.addSigners.args[0]!.name});`, functions.addSigners); c.addModifier('onlyEntryPointOrSelf', functions.addSigners); c.addFunctionCode(`_removeSigners(${functions.removeSigners.args[0]!.name});`, functions.removeSigners); c.addModifier('onlyEntryPointOrSelf', functions.removeSigners); c.addFunctionCode(`_setThreshold(${functions.setThreshold.args[0]!.name});`, functions.setThreshold); c.addModifier('onlyEntryPointOrSelf', functions.setThreshold); break; default: } } function addEIP712(c: ContractBuilder, opts: AccountOptions): void { if (opts.signatureValidation != 'ERC7739') return; c.addParent( { name: 'EIP712', path: '@openzeppelin/contracts/utils/cryptography/EIP712.sol', transpiled: false, // do not use the upgradeable variant for in Accounts }, [opts.name, '1'], ); } function overrideRawSignatureValidation(c: ContractBuilder, opts: AccountOptions): void { if (opts.signer && !opts.ERC7579Modules) return; // Signer implements _rawSignatureValidation alone if (!opts.signer && opts.ERC7579Modules) return; // AccountERC7579 implements _rawSignatureValidation alone // If no signer or ERC-7579 is used, we need to override the _rawSignatureValidation function // to provide a custom validation logic if (!opts.signer && !opts.ERC7579Modules) { // Custom validation logic c.addOverride({ name: 'Account', transpiled: false }, signerFunctions._rawSignatureValidation); c.setFunctionBody(['// Custom validation logic', 'return false;'], signerFunctions._rawSignatureValidation); } // Disambiguate between Signer and AccountERC7579 if (opts.signer && opts.ERC7579Modules) { const accountName = opts.upgradeable ? upgradeableName('AccountERC7579') : 'AccountERC7579'; const signerName = opts.upgradeable ? upgradeableName(`Signer${opts.signer}`) : `Signer${opts.signer}`; c.addImportOnly({ name: 'AbstractSigner', path: '@openzeppelin/contracts/utils/cryptography/signers/AbstractSigner.sol', transpiled: false, }); c.addOverride({ name: 'AbstractSigner', transpiled: false }, signerFunctions._rawSignatureValidation); c.addOverride({ name: 'AccountERC7579' }, signerFunctions._rawSignatureValidation); c.setFunctionComments( [ `// IMPORTANT: Make sure ${signerName} is most derived than ${accountName}`, `// in the inheritance chain (i.e. contract ... is ${accountName}, ..., ${signerName})`, '// to ensure the correct order of function resolution.', `// ${accountName} returns false for _rawSignatureValidation`, ], signerFunctions._rawSignatureValidation, ); // Base override for `_rawSignatureValidation` given MultiSignerERC7913Weighted is MultiSignerERC7913 if (opts.signer === 'MultisigWeighted') { c.addImportOnly(signers.Multisig); } } } const functions = { ...defineFunctions({ isValidSignature: { kind: 'public' as const, mutability: 'view' as const, args: [ { name: 'hash', type: 'bytes32' }, { name: 'signature', type: 'bytes calldata' }, ], returns: ['bytes4'], }, _validateUserOp: { kind: 'internal' as const, args: [ { name: 'userOp', type: 'PackedUserOperation calldata' }, { name: 'userOpHash', type: 'bytes32' }, ], returns: ['uint256'], }, _erc7821AuthorizedExecutor: { kind: 'internal' as const, args: [ { name: 'caller', type: 'address' }, { name: 'mode', type: 'bytes32' }, { name: 'executionData', type: 'bytes calldata' }, ], returns: ['bool'], mutability: 'view' as const, }, addSigners: { kind: 'public' as const, args: [{ name: 'signers', type: 'bytes[] memory' }], }, removeSigners: { kind: 'public' as const, args: [{ name: 'signers', type: 'bytes[] memory' }], }, setThreshold: { kind: 'public' as const, args: [{ name: 'threshold', type: 'uint64' }], }, setSignerWeights: { kind: 'public' as const, args: [ { name: 'signers', type: 'bytes[] memory' }, { name: 'weights', type: 'uint64[] memory' }, ], }, }), };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/OpenZeppelin/contracts-wizard'

If you have feedback or need assistance with the MCP directory API, please join our Discord server