Skip to main content
Glama

OpenZeppelin Contracts MCP Server

Official
by OpenZeppelin
erc20.ts13.5 kB
import { ContractBuilder } from './contract'; import type { Access } from './set-access-control'; import { setAccessControl, requireAccessControl } from './set-access-control'; import { addPauseFunctions } from './add-pausable'; import { defineFunctions } from './utils/define-functions'; import type { CommonOptions } from './common-options'; import { withCommonDefaults, defaults as commonDefaults } from './common-options'; import { setUpgradeable } from './set-upgradeable'; import { setInfo } from './set-info'; import { printContract } from './print'; import type { ClockMode } from './set-clock-mode'; import { clockModeDefault, setClockMode } from './set-clock-mode'; import { supportsInterface } from './common-functions'; import { OptionsError } from './error'; import { toUint256, UINT256_MAX } from './utils/convert-strings'; export const crossChainBridgingOptions = [false, 'custom', 'superchain'] as const; export type CrossChainBridging = (typeof crossChainBridgingOptions)[number]; export interface ERC20Options extends CommonOptions { name: string; symbol: string; burnable?: boolean; pausable?: boolean; premint?: string; premintChainId?: string; mintable?: boolean; callback?: boolean; permit?: boolean; /** * Whether to keep track of historical balances for voting in on-chain governance, and optionally specify the clock mode. * Setting `true` is equivalent to 'blocknumber'. Setting a clock mode implies voting is enabled. */ votes?: boolean | ClockMode; flashmint?: boolean; crossChainBridging?: CrossChainBridging; } export const defaults: Required<ERC20Options> = { name: 'MyToken', symbol: 'MTK', burnable: false, pausable: false, premint: '0', premintChainId: '', mintable: false, callback: false, permit: true, votes: false, flashmint: false, crossChainBridging: false, access: commonDefaults.access, upgradeable: commonDefaults.upgradeable, info: commonDefaults.info, } as const; export function withDefaults(opts: ERC20Options): Required<ERC20Options> { return { ...opts, ...withCommonDefaults(opts), burnable: opts.burnable ?? defaults.burnable, pausable: opts.pausable ?? defaults.pausable, premint: opts.premint || defaults.premint, premintChainId: opts.premintChainId || defaults.premintChainId, mintable: opts.mintable ?? defaults.mintable, callback: opts.callback ?? defaults.callback, permit: opts.permit ?? defaults.permit, votes: opts.votes ?? defaults.votes, flashmint: opts.flashmint ?? defaults.flashmint, crossChainBridging: opts.crossChainBridging ?? defaults.crossChainBridging, }; } export function printERC20(opts: ERC20Options = defaults): string { return printContract(buildERC20(opts)); } export function isAccessControlRequired(opts: Partial<ERC20Options>): boolean { return opts.mintable || opts.pausable || opts.upgradeable === 'uups'; } export function buildERC20(opts: ERC20Options): ContractBuilder { const allOpts = withDefaults(opts); const c = new ContractBuilder(allOpts.name); const { access, upgradeable, info } = allOpts; addBase(c, allOpts.name, allOpts.symbol); if (allOpts.crossChainBridging) { addCrossChainBridging(c, allOpts.crossChainBridging, access); } if (allOpts.premint) { addPremint(c, allOpts.premint, allOpts.premintChainId, allOpts.crossChainBridging); } if (allOpts.burnable) { addBurnable(c); } if (allOpts.pausable) { addPausableExtension(c, access); } if (allOpts.mintable) { addMintable(c, access); } if (allOpts.callback) { addCallback(c); } // Note: Votes requires Permit if (allOpts.permit || allOpts.votes) { addPermit(c, allOpts.name); } if (allOpts.votes) { const clockMode = allOpts.votes === true ? clockModeDefault : allOpts.votes; addVotes(c, clockMode); } if (allOpts.flashmint) { addFlashMint(c); } setAccessControl(c, access); setUpgradeable(c, upgradeable, access); setInfo(c, info); return c; } function addBase(c: ContractBuilder, name: string, symbol: string) { const ERC20 = { name: 'ERC20', path: '@openzeppelin/contracts/token/ERC20/ERC20.sol', }; c.addParent(ERC20, [name, symbol]); c.addOverride(ERC20, functions._update); c.addOverride(ERC20, functions._approve); // allows override from stablecoin } function addPausableExtension(c: ContractBuilder, access: Access) { const ERC20Pausable = { name: 'ERC20Pausable', path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol', }; c.addParent(ERC20Pausable); c.addOverride(ERC20Pausable, functions._update); addPauseFunctions(c, access); } function addBurnable(c: ContractBuilder) { c.addParent({ name: 'ERC20Burnable', path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol', }); } export const premintPattern = /^(\d*)(?:\.(\d+))?(?:e(\d+))?$/; export const chainIdPattern = /^(?!$)[1-9]\d*$/; export function isValidChainId(str: string): boolean { return chainIdPattern.test(str); } function scaleByPowerOfTen(base: bigint, exponent: number): bigint { if (exponent < 0) { return base / BigInt(10) ** BigInt(-exponent); } else { return base * BigInt(10) ** BigInt(exponent); } } function addPremint( c: ContractBuilder, amount: string, premintChainId: string, crossChainBridging: CrossChainBridging, ) { const m = amount.match(premintPattern); if (m) { const integer = m[1]?.replace(/^0+/, '') ?? ''; const decimals = m[2]?.replace(/0+$/, '') ?? ''; const exponent = Number(m[3] ?? 0); if (Number(integer + decimals) > 0) { const decimalPlace = decimals.length - exponent; const zeroes = new Array(Math.max(0, -decimalPlace)).fill('0').join(''); const units = integer + decimals + zeroes; const exp = decimalPlace <= 0 ? 'decimals()' : `(decimals() - ${decimalPlace})`; const validatedBaseUnits = toUint256(units, 'premint'); checkPotentialPremintOverflow(validatedBaseUnits, decimalPlace); c.addConstructorArgument({ type: 'address', name: 'recipient' }); const mintLine = `_mint(recipient, ${units} * 10 ** ${exp});`; if (crossChainBridging) { if (premintChainId === '') { throw new OptionsError({ premintChainId: 'Chain ID is required when using Premint with Cross-Chain Bridging', }); } if (!isValidChainId(premintChainId)) { throw new OptionsError({ premintChainId: 'Not a valid chain ID', }); } c.addConstructorCode(`if (block.chainid == ${premintChainId}) {`); c.addConstructorCode(` ${mintLine}`); c.addConstructorCode(`}`); } else { c.addConstructorCode(mintLine); } } } else { throw new OptionsError({ premint: 'Not a valid number', }); } } /** * Check for potential premint overflow assuming the user's contract has decimals() = 18 * * @param baseUnits The base units of the token, before applying power of 10 * @param decimalPlace If positive, the number of assumed decimal places in the least significant digits of `validatedBaseUnits`. Ignored if <= 0. * @throws OptionsError if the calculated value would overflow uint256 */ function checkPotentialPremintOverflow(baseUnits: bigint, decimalPlace: number) { const assumedExp = decimalPlace <= 0 ? 18 : 18 - decimalPlace; const calculatedValue = scaleByPowerOfTen(baseUnits, assumedExp); if (calculatedValue > UINT256_MAX) { throw new OptionsError({ premint: 'Amount would overflow uint256 after applying decimals', }); } } function addMintable(c: ContractBuilder, access: Access) { requireAccessControl(c, functions.mint, access, 'MINTER', 'minter'); c.addFunctionCode('_mint(to, amount);', functions.mint); } function addCallback(c: ContractBuilder) { const ERC1363 = { name: 'ERC1363', path: '@openzeppelin/contracts/token/ERC20/extensions/ERC1363.sol', }; c.addParent(ERC1363); c.addOverride(ERC1363, supportsInterface); } function addPermit(c: ContractBuilder, name: string) { const ERC20Permit = { name: 'ERC20Permit', path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol', }; c.addParent(ERC20Permit, [name]); c.addOverride(ERC20Permit, functions.nonces); } function addVotes(c: ContractBuilder, clockMode: ClockMode) { if (!c.parents.some(p => p.contract.name === 'ERC20Permit')) { throw new Error('Missing ERC20Permit requirement for ERC20Votes'); } const ERC20Votes = { name: 'ERC20Votes', path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol', }; c.addParent(ERC20Votes); c.addOverride(ERC20Votes, functions._update); c.addImportOnly({ name: 'Nonces', path: '@openzeppelin/contracts/utils/Nonces.sol', }); c.addOverride( { name: 'Nonces', }, functions.nonces, ); setClockMode(c, ERC20Votes, clockMode); } function addFlashMint(c: ContractBuilder) { c.addParent({ name: 'ERC20FlashMint', path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol', }); } function addCrossChainBridging(c: ContractBuilder, crossChainBridging: 'custom' | 'superchain', access: Access) { const ERC20Bridgeable = { name: 'ERC20Bridgeable', path: `@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Bridgeable.sol`, }; c.addParent(ERC20Bridgeable); c.addOverride(ERC20Bridgeable, supportsInterface); c.addOverride(ERC20Bridgeable, functions._checkTokenBridge); switch (crossChainBridging) { case 'custom': addCustomBridging(c, access); break; case 'superchain': addSuperchainERC20(c); break; default: { const _: never = crossChainBridging; throw new Error('Unknown value for `crossChainBridging`'); } } c.addVariable('error Unauthorized();'); } function addCustomBridging(c: ContractBuilder, access: Access) { switch (access) { case false: case 'ownable': { const addedBridgeImmutable = c.addVariable(`address public tokenBridge;`); if (addedBridgeImmutable) { c.addConstructorArgument({ type: 'address', name: 'tokenBridge_' }); c.addConstructorCode(`require(tokenBridge_ != address(0), "Invalid tokenBridge_ address");`); c.addConstructorCode(`tokenBridge = tokenBridge_;`); } c.setFunctionBody([`if (caller != tokenBridge) revert Unauthorized();`], functions._checkTokenBridge, 'view'); break; } case 'roles': { setAccessControl(c, access); const roleOwner = 'tokenBridge'; const roleId = 'TOKEN_BRIDGE_ROLE'; const addedRoleConstant = c.addVariable(`bytes32 public constant ${roleId} = keccak256("${roleId}");`); if (addedRoleConstant) { c.addConstructorArgument({ type: 'address', name: roleOwner }); c.addConstructorCode(`_grantRole(${roleId}, ${roleOwner});`); } c.setFunctionBody( [`if (!hasRole(${roleId}, caller)) revert Unauthorized();`], functions._checkTokenBridge, 'view', ); break; } case 'managed': { setAccessControl(c, access); c.addImportOnly({ name: 'AuthorityUtils', path: `@openzeppelin/contracts/access/manager/AuthorityUtils.sol`, }); c.setFunctionBody( [ `(bool immediate,) = AuthorityUtils.canCallWithDelay(authority(), caller, address(this), bytes4(_msgData()[0:4]));`, `if (!immediate) revert Unauthorized();`, ], functions._checkTokenBridge, 'view', ); break; } default: { const _: never = access; throw new Error('Unknown value for `access`'); } } } function addSuperchainERC20(c: ContractBuilder) { c.addVariable('address internal constant SUPERCHAIN_TOKEN_BRIDGE = 0x4200000000000000000000000000000000000028;'); c.setFunctionBody( ['if (caller != SUPERCHAIN_TOKEN_BRIDGE) revert Unauthorized();'], functions._checkTokenBridge, 'pure', ); c.setFunctionComments( [ '/**', ' * @dev Checks if the caller is the predeployed SuperchainTokenBridge. Reverts otherwise.', ' *', ' * IMPORTANT: The predeployed SuperchainTokenBridge is only available on chains in the Superchain.', ' */', ], functions._checkTokenBridge, ); } export const functions = defineFunctions({ _update: { kind: 'internal' as const, args: [ { name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'value', type: 'uint256' }, ], }, _approve: { kind: 'internal' as const, args: [ { name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }, { name: 'value', type: 'uint256' }, { name: 'emitEvent', type: 'bool' }, ], }, mint: { kind: 'public' as const, args: [ { name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }, ], }, pause: { kind: 'public' as const, args: [], }, unpause: { kind: 'public' as const, args: [], }, snapshot: { kind: 'public' as const, args: [], }, nonces: { kind: 'public' as const, args: [{ name: 'owner', type: 'address' }], returns: ['uint256'], mutability: 'view' as const, }, _checkTokenBridge: { kind: 'internal' as const, args: [{ name: 'caller', type: 'address' }], }, });

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