diff --git a/docs/config-md.js b/docs/config-md.js
new file mode 100644
index 00000000000..afbf7c6a58b
--- /dev/null
+++ b/docs/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/templates-md/contract.hbs b/docs/templates-md/contract.hbs
new file mode 100644
index 00000000000..c456021ce27
--- /dev/null
+++ b/docs/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}})
+
+
+
+
+
+{{{process-natspec natspec.dev}}}
+
+
+
+
+{{/each}}
+
+{{#each functions}}
+
+
+
+
+
{{{name}}}({{typed-params params}}){{#if returns2}} â {{typed-params returns2}}{{/if}}
+
+
+
+
+{{{process-natspec natspec.dev}}}
+
+
+
+
+{{/each}}
+
+{{#each events}}
+
+
+
+
+
{{{name}}}({{typed-params params}})
+
+
+
+
+
+{{{process-natspec natspec.dev}}}
+
+
+
+{{/each}}
+
+{{#each errors}}
+
+
+
+
+
{{{name}}}({{typed-params params}})
+
+
+
+
+{{{process-natspec natspec.dev}}}
+
+
+
+
+{{/each}}
diff --git a/docs/templates-md/helpers.js b/docs/templates-md/helpers.js
new file mode 100644
index 00000000000..214682817db
--- /dev/null
+++ b/docs/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, '')
+ .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/templates-md/page.hbs b/docs/templates-md/page.hbs
new file mode 100644
index 00000000000..11c79513764
--- /dev/null
+++ b/docs/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/templates-md/properties.js b/docs/templates-md/properties.js
new file mode 100644
index 00000000000..f2453b63bb4
--- /dev/null
+++ b/docs/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/hardhat.config.js b/hardhat.config.js
index ca04c6c098b..9676ba2d6f5 100644
--- a/hardhat.config.js
+++ b/hardhat.config.js
@@ -121,5 +121,5 @@ module.exports = {
paths: {
sources: argv.src,
},
- docgen: require('./docs/config'),
+ docgen: require('./docs/config-md'),
};