diff --git a/.changeset/library-constellation-map.md b/.changeset/library-constellation-map.md new file mode 100644 index 00000000..bc2b1c69 --- /dev/null +++ b/.changeset/library-constellation-map.md @@ -0,0 +1,9 @@ +--- +--- + +docs: add an interactive constellation map to the library index + +Generate a deterministic library catalog from Solidity sources and CLI presets, +link documented facets in new tabs, and provide a responsive 3D star map with an +automatic accessible 2D fallback. Rebuild documentation when CLI or Solidity +sources change. diff --git a/.github/scripts/netlify-ignore.test.mjs b/.github/scripts/netlify-ignore.test.mjs new file mode 100644 index 00000000..e52e317d --- /dev/null +++ b/.github/scripts/netlify-ignore.test.mjs @@ -0,0 +1,59 @@ +import assert from 'node:assert/strict'; +import { execFileSync, spawnSync } from 'node:child_process'; +import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; + +const script = fileURLToPath(new URL('../../website/netlify-ignore.sh', import.meta.url)).replaceAll('\\', '/'); +const shell = process.env.NETLIFY_IGNORE_TEST_SHELL || 'bash'; + +test('Netlify rebuilds for docs inputs and skips unrelated changes', async (t) => { + const root = await mkdtemp(path.join(os.tmpdir(), 'compose-netlify-ignore-')); + t.after(() => rm(root, { recursive: true, force: true })); + const git = (...args) => execFileSync('git', ['-C', root, ...args], { encoding: 'utf8', windowsHide: true }).trim(); + await mkdir(path.join(root, 'website'), { recursive: true }); + await writeFile(path.join(root, 'website/index.mdx'), '# Library\n'); + git('init', '--quiet'); + function commit() { + git('add', '.'); + git('-c', 'user.name=Docs Test', '-c', 'user.email=docs-test@example.invalid', '-c', 'commit.gpgsign=false', 'commit', '--quiet', '-m', 'Test fixture'); + return git('rev-parse', 'HEAD'); + } + let previous = commit(); + function check(cached, current) { + const result = spawnSync(shell, [script], { + cwd: path.join(root, 'website'), + env: { ...process.env, CACHED_COMMIT_REF: cached, COMMIT_REF: current }, + encoding: 'utf8', + windowsHide: true, + }); + if (result.error) throw result.error; + assert.equal(result.stderr, ''); + return result.status; + } + for (const [file, expected] of [ + ['website/index.mdx', 1], + ['cli/bases/example.json', 1], + ['cli/src/example.ts', 1], + ['src/ExampleFacet.sol', 1], + ['src/token/example/ExampleFacet.sol', 1], + ['src/README.md', 0], + ['README.md', 0], + ]) { + await t.test(file, async () => { + await mkdir(path.dirname(path.join(root, file)), { recursive: true }); + await writeFile(path.join(root, file), `Changed ${file}\n`); + const current = commit(); + assert.equal(check(previous, current), expected); + previous = current; + }); + } + await t.test('missing or untrustworthy baseline always builds', () => { + assert.equal(check('', previous), 1); + assert.equal(check(previous, ''), 1); + assert.equal(check(previous, previous), 1); + assert.equal(check('nonexistent-ref', previous), 1); + }); +}); diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index 5ff051b2..1817c82b 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -4,11 +4,18 @@ on: pull_request: paths: - 'website/**' + - 'cli/**' + - 'src/**/*.sol' - '.github/workflows/docs-build.yml' + - '.github/scripts/netlify-ignore.test.mjs' push: branches: [main] paths: - 'website/**' + - 'cli/**' + - 'src/**/*.sol' + - '.github/workflows/docs-build.yml' + - '.github/scripts/netlify-ignore.test.mjs' jobs: lint-and-build: @@ -25,6 +32,9 @@ jobs: node-version: '20' cache: 'npm' + - name: Check docs rebuild rules + run: node --test .github/scripts/netlify-ignore.test.mjs + - name: Install dependencies run: npm ci diff --git a/website/README.md b/website/README.md index eff415d0..3df14ebd 100644 --- a/website/README.md +++ b/website/README.md @@ -33,4 +33,49 @@ This command generates static content into the `build` directory and can be serv ```bash npm run generate-docs -``` \ No newline at end of file +``` + +## Library Catalog Snapshot + +`src/data/libraryCatalog.json` is generated from this checkout's `../src` Solidity +files and `../cli/bases` presets. Website components can import the JSON without +GitHub access or runtime requests. Both `npm start` and `npm run build` regenerate +it before Docusaurus runs. After editing sources or presets during development, +run `npm run catalog:generate` again. + +```bash +npm run catalog:generate +npm run catalog:check +npm run catalog:test +``` + +The checked-in snapshot provides a reviewable baseline. Builds regenerate it +from the checkout, so source and preset PRs do not need a bot commit to refresh +the deployed graph. Refresh the baseline when changing the generator or preparing +a catalog update. The check command detects a stale baseline. Output is +deterministic and normalizes CRLF line endings, with a content hash instead of a +build timestamp or changing HEAD. + +`files` inventories all Solidity source paths, with filename-based kinds, +category/family grouping and preset memberships. Grouping describes directory +membership, not Solidity dependencies. `presets` preserves each CLI definition +and its source file, plus resolved references with required/optional roles and +exact metadata field paths. CLI-local templates (such as Counter) remain +`external` references and do not become library facets. Missing package source +references fail generation. + +The Library index imports the snapshot through `src/components/LibraryGraph`. +It uses Docusaurus document IDs and their published routes to link available +facet pages, including custom slugs, in new tabs. Facets without a matching doc +remain inspectable. CLI preset and search filters change emphasis without +removing or repositioning nodes. The 3D view loads Three.js only in the browser, +with an automatic keyboard-operable SVG fallback when WebGL is unavailable and +a facet list for direct navigation. + +Run `npm run graph:test` to check route matching, preset membership and stable +layout. Grouping edges represent family membership, not Solidity dependencies. + +The map uses Babylon's constellation arrangement: feature families occupy +different depths, with small glowing facet stars and an oblique orbit camera. +Portrait screens rotate the same sky into a taller composition. The SVG view +uses the same camera projection, so switching renderers preserves the map. diff --git a/website/docs/library/index.mdx b/website/docs/library/index.mdx index 5091386c..e8266489 100644 --- a/website/docs/library/index.mdx +++ b/website/docs/library/index.mdx @@ -9,6 +9,7 @@ import DocCard, { DocCardGrid } from '@site/src/components/docs/DocCard'; import DocSubtitle from '@site/src/components/docs/DocSubtitle'; import Icon from '@site/src/components/ui/Icon'; import Callout from '@site/src/components/ui/Callout'; +import LibraryGraph from '@site/src/components/LibraryGraph'; # Compose Smart Contracts Library @@ -16,6 +17,8 @@ import Callout from '@site/src/components/ui/Callout'; Contract-level docs for each **Compose** module and facet. What to call on-chain, how state is organized, and how upgrades work. + + /dev/null; then +# Library catalog generation reads Solidity sources and CLI presets at build time. +if git diff --quiet "$CACHED_COMMIT_REF" "$COMMIT_REF" -- \ + website/ cli/ ':(top,glob)src/**/*.sol' 2>/dev/null; then exit 0 fi diff --git a/website/netlify.toml b/website/netlify.toml index 5219fafe..03b89b01 100644 --- a/website/netlify.toml +++ b/website/netlify.toml @@ -8,7 +8,7 @@ # Publish directory (relative to base directory) publish = "build" - # Skip Netlify builds if there are no changes in the website/ directory + # Rebuild for website, CLI, or Solidity source changes # Based on official Netlify documentation: https://docs.netlify.com/configure-builds/ignore-builds/ # Works for both regular PRs and forked PRs ignore = "./netlify-ignore.sh" @@ -29,4 +29,4 @@ to = "https://us.i.posthog.com/:splat" host = "us.i.posthog.com" status = 200 - force = true \ No newline at end of file + force = true diff --git a/website/package.json b/website/package.json index e32156cc..f0948b05 100644 --- a/website/package.json +++ b/website/package.json @@ -4,7 +4,13 @@ "private": true, "scripts": { "docusaurus": "docusaurus", + "catalog:generate": "node scripts/generate-library-catalog.mjs", + "catalog:check": "node scripts/generate-library-catalog.mjs --check", + "catalog:test": "node --test scripts/generate-library-catalog.test.mjs", + "graph:test": "node --test scripts/library-graph.test.mjs", + "prestart": "npm run catalog:generate", "start": "docusaurus start", + "prebuild": "npm run catalog:generate", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", diff --git a/website/scripts/generate-library-catalog.mjs b/website/scripts/generate-library-catalog.mjs new file mode 100644 index 00000000..43a0efe3 --- /dev/null +++ b/website/scripts/generate-library-catalog.mjs @@ -0,0 +1,126 @@ +import { createHash } from 'node:crypto'; +import { readdir, readFile, mkdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const defaultRoot = fileURLToPath(new URL('../../', import.meta.url)); +const packagePrefix = '@perfect-abstractions/compose/'; +const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0; + +async function filesUnder(root, directory, extension) { + const entries = await readdir(path.join(root, directory), { withFileTypes: true }); + const files = []; + for (const entry of entries) { + const relative = `${directory}/${entry.name}`; + if (entry.isDirectory()) files.push(...await filesUnder(root, relative, extension)); + else if (entry.isFile() && entry.name.endsWith(extension)) files.push(relative); + } + return files.sort(compare); +} + +/** Build a local source inventory with CLI preset membership as a separate overlay. */ +export async function buildLibraryCatalog(root = defaultRoot) { + const sourcePaths = await filesUnder(root, 'src', '.sol'); + const presetPaths = await filesUnder(root, 'cli/bases', '.json'); + const inputs = new Map(); + for (const relative of [...sourcePaths, ...presetPaths]) { + inputs.set(relative, (await readFile(path.join(root, relative), 'utf8')).replace(/\r\n/g, '\n')); + } + const files = sourcePaths.map((relative) => { + const name = path.posix.basename(relative, '.sol'); + const parts = relative.split('/'); + const kind = parts[1] === 'interfaces' ? 'interface' + : name.endsWith('Facet') ? 'facet' + : name.endsWith('Mod') ? 'module' + : 'other'; + return { + path: relative, + name, + kind, + category: parts[1], + family: parts.length > 3 ? parts[2] : null, + sha256: createHash('sha256').update(inputs.get(relative)).digest('hex'), + presets: [], + }; + }); + const byPath = new Map(files.map((file) => [file.path, file])); + const presets = []; + const ids = new Set(); + + for (const sourcePath of presetPaths) { + const definitions = JSON.parse(inputs.get(sourcePath)); + if (!definitions || Array.isArray(definitions) || typeof definitions !== 'object') { + throw new Error(`Expected preset definitions in ${sourcePath}`); + } + for (const id of Object.keys(definitions).sort(compare)) { + if (ids.has(id)) throw new Error(`Duplicate preset ID: ${id}`); + ids.add(id); + const definition = definitions[id]; + if (!definition || typeof definition.label !== 'string') { + throw new Error(`Missing preset label: ${sourcePath}#${id}`); + } + const references = []; + // Preserve exact field paths, including mod and erc165 references, not just facets. + function visit(value, fields = []) { + if (typeof value === 'string' && value.endsWith('.sol')) { + const field = fields.join('.'); + const libraryReference = value.startsWith(packagePrefix); + const resolvedPath = libraryReference ? `src/${value.slice(packagePrefix.length)}` : null; + const source = resolvedPath ? byPath.get(resolvedPath) : undefined; + const status = source ? 'resolved' : libraryReference ? 'missing' : 'external'; + const role = ['required', 'optional'].includes(fields[0]) ? fields[0] : null; + references.push({ field, declaredPath: value, path: source?.path ?? null, role, status }); + if (source) source.presets.push({ id, field, role }); + } else if (Array.isArray(value)) { + value.forEach((child, index) => visit(child, [...fields, String(index)])); + } else if (value && typeof value === 'object') { + for (const key of Object.keys(value).sort(compare)) visit(value[key], [...fields, key]); + } + } + visit(definition); + presets.push({ id, sourcePath, definition, references }); + } + } + const hash = createHash('sha256'); + for (const [relative, contents] of inputs) { + hash.update(JSON.stringify([relative, contents])); + } + return { + schemaVersion: 1, + source: { directories: ['src', 'cli/bases'], sha256: hash.digest('hex') }, + files, + presets: presets.sort((a, b) => compare(a.id, b.id)), + }; +} + +export async function generateLibraryCatalog({ root = defaultRoot, check = false } = {}) { + const catalog = await buildLibraryCatalog(root); + const missing = catalog.presets.flatMap((preset) => preset.references + .filter((reference) => reference.status === 'missing') + .map((reference) => `${preset.id}:${reference.field} -> ${reference.declaredPath}`)); + if (missing.length) throw new Error(`Unresolved Compose references:\n${missing.join('\n')}`); + const output = path.join(root, 'website/src/data/libraryCatalog.json'); + const serialized = `${JSON.stringify(catalog, null, 2)}\n`; + let current; + try { current = await readFile(output, 'utf8'); } + catch (error) { if (error.code !== 'ENOENT') throw error; } + if (check) { + if (current?.replace(/\r\n/g, '\n') !== serialized) { + throw new Error('Library catalog is stale. Run npm run catalog:generate in website.'); + } + } else if (current?.replace(/\r\n/g, '\n') !== serialized) { + await mkdir(path.dirname(output), { recursive: true }); + await writeFile(output, serialized); + } + return catalog; +} + +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + try { + const catalog = await generateLibraryCatalog({ check: process.argv.includes('--check') }); + console.log(`Library catalog: ${catalog.files.filter((file) => file.kind === 'facet').length} facets, ${catalog.presets.length} presets`); + } catch (error) { + console.error(error.message); + process.exitCode = 1; + } +} diff --git a/website/scripts/generate-library-catalog.test.mjs b/website/scripts/generate-library-catalog.test.mjs new file mode 100644 index 00000000..73d8f223 --- /dev/null +++ b/website/scripts/generate-library-catalog.test.mjs @@ -0,0 +1,85 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import test from 'node:test'; +import { buildLibraryCatalog, generateLibraryCatalog } from './generate-library-catalog.mjs'; + +async function fixture(t) { + const root = await mkdtemp(path.join(os.tmpdir(), 'compose-catalog-')); + t.after(() => rm(root, { recursive: true, force: true })); + const sources = { + 'src/token/Example/Data/ExampleDataFacet.sol': 'contract ExampleDataFacet {}\n', + 'src/token/Example/Data/ExampleDataMod.sol': 'library ExampleDataMod {}\n', + 'src/token/Example/Burn/ExampleBurnFacet.sol': 'contract ExampleBurnFacet {}\n', + 'src/interfaces/IExample.sol': 'interface IExample {}\n', + 'src/interfaces/IFacet.sol': 'interface IFacet {}\n', + }; + for (const [relative, contents] of Object.entries(sources)) { + await mkdir(path.dirname(path.join(root, relative)), { recursive: true }); + await writeFile(path.join(root, relative), contents); + } + const definitions = { + example: { + label: 'Example', + erc165: { path: '@perfect-abstractions/compose/interfaces/IExample.sol' }, + required: { + ExampleDataFacet: { + path: '@perfect-abstractions/compose/token/Example/Data/ExampleDataFacet.sol', + mod: '@perfect-abstractions/compose/token/Example/Data/ExampleDataMod.sol', + }, + }, + optional: { LocalFacet: { path: './src/templates/LocalFacet.sol' } }, + }, + }; + await mkdir(path.join(root, 'cli/bases'), { recursive: true }); + const presetFile = path.join(root, 'cli/bases/example.json'); + await writeFile(presetFile, JSON.stringify(definitions)); + return { root, definitions, presetFile, sources }; +} + +test('inventories facets outside presets and preserves exact preset references', async (t) => { + const { root, definitions } = await fixture(t); + const catalog = await buildLibraryCatalog(root); + assert.equal(catalog.files.filter((file) => file.kind === 'facet').length, 2); + assert.equal(catalog.files.find((file) => file.name === 'IFacet').kind, 'interface'); + const data = catalog.files.find((file) => file.name === 'ExampleDataFacet'); + assert.equal(data.category, 'token'); + assert.equal(data.family, 'Example'); + assert.deepEqual(data.presets, [{ id: 'example', field: 'required.ExampleDataFacet.path', role: 'required' }]); + assert.deepEqual(catalog.files.find((file) => file.name === 'ExampleBurnFacet').presets, []); + assert.deepEqual(catalog.presets[0].definition, definitions.example); + assert.equal(catalog.presets[0].references.find((ref) => ref.field.endsWith('.mod')).status, 'resolved'); + assert.equal(catalog.presets[0].references.find((ref) => ref.field === 'erc165.path').role, null); + const external = catalog.presets[0].references.find((ref) => ref.status === 'external'); + assert.equal(external.path, null); + assert.equal(external.declaredPath, './src/templates/LocalFacet.sol'); +}); + +test('generation is deterministic across line endings and detects stale inputs', async (t) => { + const { root, sources } = await fixture(t); + const first = await generateLibraryCatalog({ root }); + for (const [relative, contents] of Object.entries(sources)) { + await writeFile(path.join(root, relative), contents.replace(/\n/g, '\r\n')); + } + assert.deepEqual(await generateLibraryCatalog({ root, check: true }), first); + const output = path.join(root, 'website/src/data/libraryCatalog.json'); + const before = await readFile(output, 'utf8'); + await generateLibraryCatalog({ root }); + assert.equal(await readFile(output, 'utf8'), before); + await writeFile(path.join(root, Object.keys(sources)[0]), 'contract ChangedFacet {}\n'); + await assert.rejects(generateLibraryCatalog({ root, check: true }), /stale/); +}); + +test('missing package sources fail generation instead of inventing library nodes', async (t) => { + const { root, definitions, presetFile } = await fixture(t); + definitions.example.required.ExampleDataFacet.path = '@perfect-abstractions/compose/MissingFacet.sol'; + await writeFile(presetFile, JSON.stringify(definitions)); + await assert.rejects(generateLibraryCatalog({ root }), /Unresolved Compose references/); +}); + +test('duplicate preset IDs are rejected', async (t) => { + const { root, definitions } = await fixture(t); + await writeFile(path.join(root, 'cli/bases/duplicate.json'), JSON.stringify(definitions)); + await assert.rejects(buildLibraryCatalog(root), /Duplicate preset ID/); +}); diff --git a/website/scripts/library-graph.test.mjs b/website/scripts/library-graph.test.mjs new file mode 100644 index 00000000..65b8be23 --- /dev/null +++ b/website/scripts/library-graph.test.mjs @@ -0,0 +1,66 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { readFile } from 'node:fs/promises'; +import { libraryGraph, constellationView, starDepthScale } from '../src/components/LibraryGraph/model.mjs'; + +const catalog = JSON.parse(await readFile(new URL('../src/data/libraryCatalog.json', import.meta.url), 'utf8')); + +test('only actual Docusaurus routes become facet links, including custom slugs', () => { + const graph = libraryGraph(catalog, [{ id: 'library/token/ERC20/Data/ERC20DataFacet', path: '/custom-data-route/' }]); + assert.equal(graph.nodes.find((node) => node.name === 'ERC20DataFacet').href, '/custom-data-route/'); + assert.equal(graph.nodes.find((node) => node.name === 'ERC20TransferFacet').href, undefined); + assert.ok(graph.nodes.filter((node) => node.kind === 'facet').every((node) => node.category !== 'interfaces')); +}); + +test('preset and search emphasis preserve complete membership and stable positions', () => { + const full = libraryGraph(catalog); + const filtered = libraryGraph(catalog, [], { preset: 'erc-20', query: 'Data' }); + assert.deepEqual(filtered.nodes.map((node) => [node.id, node.position]), full.nodes.map((node) => [node.id, node.position])); + const matching = filtered.nodes.filter((node) => node.kind === 'facet' && node.matches); + assert.deepEqual(matching.map((node) => node.name), ['ERC20DataFacet', 'ERC20MetadataFacet']); + const ids = new Set(full.nodes.map((node) => node.id)); + assert.ok(full.links.every((link) => ids.has(link.source) && ids.has(link.target))); +}); + +test('CLI-local templates do not fabricate library facets', () => { + const graph = libraryGraph(catalog, [], { preset: 'counter' }); + assert.equal(graph.nodes.filter((node) => node.matches).length, 0); + assert.ok(graph.nodes.every((node) => !node.name.startsWith('Counter'))); +}); + +test('constellations and their facets occupy depth rather than a common plane', () => { + const graph = libraryGraph(catalog); + const groups = graph.nodes.filter((node) => node.kind === 'group'); + const depth = groups.map((node) => node.position[2]); + assert.ok(Math.max(...depth) - Math.min(...depth) > 10); + const erc20 = graph.nodes.filter((node) => node.parentId === 'token/ERC20'); + const localDepth = erc20.map((node) => node.position[2]); + assert.ok(Math.max(...localDepth) - Math.min(...localDepth) > 1.5); + assert.ok(graph.links.some((edge) => edge.source === 'category:token' && edge.target === 'token/ERC20')); +}); + +test('centers from different categories form a volume and maintain near/far size cues', () => { + const { nodes } = libraryGraph(catalog); + const [a, b, c, d] = ['access/Owner', 'diamond/', 'interfaceDetection/ERC165', 'token/ERC1155'] + .map((id) => nodes.find((node) => node.id === id).position); + const subtract = (p, q) => p.map((value, axis) => value - q[axis]); + const ab = subtract(b, a), ac = subtract(c, a), ad = subtract(d, a); + const cross = [ac[1] * ad[2] - ac[2] * ad[1], ac[2] * ad[0] - ac[0] * ad[2], ac[0] * ad[1] - ac[1] * ad[0]]; + const volume = Math.abs(ab.reduce((sum, value, axis) => sum + value * cross[axis], 0)); + assert.ok(volume / (Math.hypot(...ab) * Math.hypot(...ac) * Math.hypot(...ad)) > 0.2); + assert.ok(starDepthScale(15, 25) > starDepthScale(35, 25)); + assert.ok(starDepthScale(100, 25) >= 0.72); + assert.ok(starDepthScale(0, 25) <= 1.45); +}); + +test('oblique desktop and portrait cameras frame every node with label margins', () => { + const { nodes } = libraryGraph(catalog); + for (const aspect of [2.2, 1.5, 0.8, 0.6]) { + const view = constellationView(nodes, aspect); + assert.equal(view.positions.size, nodes.length); + assert.ok(view.camera[1] < view.target[1]); + for (const projected of view.projected.values()) { + assert.ok(projected.every((value) => Number.isFinite(value) && Math.abs(value) < 0.9)); + } + } +}); diff --git a/website/src/components/LibraryGraph/index.js b/website/src/components/LibraryGraph/index.js new file mode 100644 index 00000000..4acc747d --- /dev/null +++ b/website/src/components/LibraryGraph/index.js @@ -0,0 +1,111 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useAllDocsData } from '@docusaurus/plugin-content-docs/client'; +import { useColorMode } from '@docusaurus/theme-common'; +import catalog from '@site/src/data/libraryCatalog.json'; +import Icon from '../ui/Icon'; +import { libraryGraph, constellationView } from './model.mjs'; +import styles from './styles.module.css'; + +function FlatGraph({ data, onNode, selected }) { + const svg = useRef(null); + const [size, setSize] = useState({ width: 1000, height: 500 }); + useEffect(() => { + const observer = new ResizeObserver(([entry]) => { + if (entry.contentRect.width && entry.contentRect.height) setSize({ width: entry.contentRect.width, height: entry.contentRect.height }); + }); + observer.observe(svg.current); + return () => observer.disconnect(); + }, []); + const byId = new Map(data.nodes.map((node) => [node.id, node])); + const view = constellationView(data.nodes, size.width / size.height); + const point = (node) => { + const [x, y] = view.projected.get(node.id); + return [(x + 1) / 2 * size.width, (1 - y) / 2 * size.height]; + }; + const labelPositions = new Map(); + const placed = []; + for (const node of data.nodes.filter((item) => item.kind === 'group')) { + const [anchorX, anchorY] = point(node); + const width = node.name.length * (node.level === 'category' ? 6.3 : 7.5) + 10; + const x = Math.max(width / 2 + 6, Math.min(size.width - width / 2 - 6, anchorX)); + for (const offset of [node.level === 'category' ? 22 : -13, -30, 35, -48, 53]) { + const y = anchorY + offset; + if (y < 20 || y > size.height - 6 || placed.some((rect) => x + width / 2 > rect.left && x - width / 2 < rect.right && y > rect.top && y - 18 < rect.bottom)) continue; + labelPositions.set(node.id, [x, y]); + placed.push({ left: x - width / 2 - 3, right: x + width / 2 + 3, top: y - 21, bottom: y + 3 }); + break; + } + } + return + {Array.from({ length: 130 }, (_, i) => )} + {data.links.map((edge) => { + const [x1, y1] = point(byId.get(edge.source)), [x2, y2] = point(byId.get(edge.target)); + const node = byId.get(edge.target); + return ; + })} + {data.nodes.map((node) => { + const [x, y] = point(node); + const content = <> + + + + {labelPositions.has(node.id) && {node.name}} + {node.name} + ; + return onNode(node)} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); onNode(node); } }}>{content}; + })} + ; +} + +export default function LibraryGraph() { + const allDocs = useAllDocsData(); + const { colorMode } = useColorMode(); + const docs = allDocs.default?.versions[0]?.docs ?? []; + const [query, setQuery] = useState(''); + const [preset, setPreset] = useState(''); + const [failed, setFailed] = useState(false); + const [selected, setSelected] = useState(); + const [hovered, setHovered] = useState(); + const [browse, setBrowse] = useState(false); + const data = useMemo(() => libraryGraph(catalog, docs, { query, preset }), [docs, query, preset]); + const facets = data.nodes.filter((node) => node.kind === 'facet'); + const matching = facets.filter((node) => node.matches); + const host = useRef(null); + const scene = useRef(null); + const onNode = useCallback((node) => { + if (node.kind === 'group') { setQuery(node.level === 'category' || node.category === 'diamond' ? node.category : node.name); return; } + setSelected(node); + if (node.href) window.open(node.href, '_blank', 'noopener,noreferrer'); + }, []); + const flat = failed; + useEffect(() => { + if (flat) return undefined; + let cancelled = false; + import('./scene').then(({ createLibraryScene }) => { + if (cancelled || !host.current) return; + try { + scene.current = createLibraryScene(host.current, data, { onNode, onHover: setHovered, onError: () => setFailed(true), dark: colorMode === 'dark' }); + } catch { setFailed(true); } + }).catch(() => { if (!cancelled) setFailed(true); }); + return () => { cancelled = true; scene.current?.dispose(); scene.current = null; }; + }, [flat, data, colorMode, onNode]); + useEffect(() => { scene.current?.select(selected?.id); }, [selected]); + const reset = () => { setQuery(''); setPreset(''); setSelected(undefined); setHovered(undefined); scene.current?.reset(); }; + const detail = hovered?.kind === 'facet' ? hovered : selected; + return
+

Library map

{facets.length} facets
+
+ setQuery(event.target.value)} /> + + +
+
+ {flat ? :
} +
{detail ? <>{detail.name}{detail.href ? Documentation : Not documented yet} : {matching.length} matching facets ยท {facets.filter((node) => node.href).length} documented}
+
+
Family / facet
+ {browse &&
    {matching.map((node) =>
  • {node.href ? {node.name} : }
  • )}{matching.length === 0 &&
  • No matching facets
  • }
} +
; +} diff --git a/website/src/components/LibraryGraph/model.mjs b/website/src/components/LibraryGraph/model.mjs new file mode 100644 index 00000000..0b33d889 --- /dev/null +++ b/website/src/components/LibraryGraph/model.mjs @@ -0,0 +1,110 @@ +const colors = ['#aca0ff', '#83b6f4', '#67d5e0', '#8cc7ed', '#6ed8b3', '#e9b76c', '#f28cab', '#be99f0']; +const categoryLabels = { access: 'Access Control', diamond: 'Diamond Core', interfaceDetection: 'Interface Detection', token: 'Tokens' }; +// Babylon's noncoplanar constellation anchors preserve recognizable neighborhoods. +const anchors = { + 'access/AccessControl': [-8.689, 6.059, -0.66], + 'access/Owner': [-4.122, 3.062, -0.18], + 'diamond/': [-7.26648, -0.02962, 1.2], + 'interfaceDetection/ERC165': [-0.22698, -3.28612, 1.08], + 'token/ERC20': [1.047, 0.722, -0.12], + 'token/ERC721': [5.827, 0.154, -0.36], + 'token/ERC1155': [6.183, -3.343, 1.44], + 'token/ERC6909': [2.617, -3.173, 0.54], + 'token/Royalty': [0.369, 5.825, -1.44], +}; +// Move whole constellations along the viewing axis, independently of their x/y grouping. +const constellationDepths = { + 'access/AccessControl': -3.8, + 'access/Owner': 3.2, + 'diamond/': 5.8, + 'interfaceDetection/ERC165': -4.4, + 'token/ERC20': 1.5, + 'token/ERC721': -5.5, + 'token/ERC1155': 4.4, + 'token/ERC6909': -2.8, + 'token/Royalty': 5.5, +}; +const familyColors = { ERC20: '#e9b76c', ERC721: '#be99f0', ERC1155: '#6ed8b3', ERC6909: '#f28cab', Royalty: '#83b6f4' }; +const categoryAnchors = { access: [-6.4055, 4.5605, -0.42], token: [3.2086, 0.037, 0.012], interfaceDetection: [-1.65048, -2.02408, 0.66] }; + +/** Directory membership defines the map. Presets only change emphasis. */ +export function libraryGraph(catalog, docs = [], { query = '', preset = '' } = {}) { + const routes = new Map(docs.map((doc) => [doc.id, doc.path])); + const facets = catalog.files.filter((file) => file.kind === 'facet'); + const families = [...new Set(facets.map((file) => `${file.category}/${file.family ?? ''}`))]; + const nodes = []; + const links = []; + const term = query.trim().toLowerCase(); + for (const [index, familyId] of families.entries()) { + const members = facets.filter((file) => `${file.category}/${file.family ?? ''}` === familyId); + const [category, family] = familyId.split('/'); + const phase = index * 2.399963; + const anchor = anchors[familyId] ?? [Math.cos(phase) * 9, Math.sin(phase) * 6, Math.sin(index * 1.7) * 3]; + const center = anchor.map((value, axis) => value + back[axis] * (constellationDepths[familyId] ?? Math.sin(index * 2.1) * 5)); + const group = { id: familyId, name: family || categoryLabels[category] || category, kind: 'group', color: familyColors[family] ?? colors[index % colors.length], position: center, category, matches: false }; + nodes.push(group); + members.forEach((file, i) => { + const angle = i / members.length * Math.PI * 2 + 0.36; + const radius = members.length === 1 ? 0.68 : 1.1 + (i % 3) * 0.22; + const matches = (!term || file.path.toLowerCase().includes(term) || file.name.toLowerCase().includes(term)) + && (!preset || file.presets.some((membership) => membership.id === preset)); + const node = { ...file, id: file.path, kind: 'facet', color: group.color, parentId: familyId, matches, + href: routes.get(`library/${file.path.slice(4, -4)}`), + position: [center[0] + Math.cos(angle) * radius, center[1] + Math.sin(angle) * radius * 0.79, center[2] + Math.sin(i * 1.7) * (members.length >= 4 ? 1.35 : 0.3)] }; + group.matches ||= matches; + nodes.push(node); + links.push({ source: familyId, target: file.path }); + }); + } + for (const [category, anchor] of Object.entries(categoryAnchors)) { + const children = nodes.filter((node) => node.kind === 'group' && node.category === category); + if (!children.length) continue; + const childAnchor = anchors[children[0].id] ?? children[0].position; + const position = children.length === 1 + ? anchor.map((value, axis) => value + children[0].position[axis] - childAnchor[axis]) + : [0, 1, 2].map((axis) => children.reduce((sum, node) => sum + node.position[axis], 0) / children.length); + const id = `category:${category}`; + nodes.push({ id, name: categoryLabels[category], kind: 'group', level: 'category', category, position, color: category === 'token' ? '#f0c17e' : '#aca0ff', matches: children.some((node) => node.matches) }); + children.forEach((node) => links.push({ source: id, target: node.id })); + } + return { nodes, links }; +} + +const dot = (a, b) => a.reduce((sum, value, i) => sum + value * b[i], 0); +const length = Math.hypot(0.24, -0.38, 1); +const back = [0.24, -0.38, 1].map((value) => value / length); +const horizontal = Math.hypot(back[0], back[2]); +const right = [back[2] / horizontal, 0, -back[0] / horizontal]; +const up = [-back[1] * back[0] / horizontal, horizontal, -back[1] * back[2] / horizontal]; +const fromView = (point) => [0, 1, 2].map((i) => right[i] * point[0] + up[i] * point[1] + back[i] * point[2]); + +/** Retain a readable minimum while making near/far stars visually distinct. */ +export function starDepthScale(distance, referenceDistance) { + return Math.min(1.45, Math.max(0.72, (referenceDistance / Math.max(0.1, distance)) ** 0.65)); +} + +/** The same oblique camera and portrait rotation drive WebGL and the SVG fallback. */ +export function constellationView(nodes, aspect) { + const view = nodes.map((node) => { + const p = [dot(node.position, right), dot(node.position, up), dot(node.position, back)]; + return { id: node.id, point: aspect < 1 ? [-p[1], p[0], p[2]] : p }; + }); + const center = [0, 1, 2].map((axis) => { + const values = view.map(({ point }) => point[axis]); + return (Math.min(...values) + Math.max(...values)) / 2; + }); + const tangent = Math.tan(43 * Math.PI / 360); + const distance = Math.max(12, ...view.map(({ point }) => point[2] - center[2] + Math.max( + (Math.abs(point[0] - center[0]) + 1.2) / (tangent * aspect * 0.9), + (Math.abs(point[1] - center[1]) + 1.2) / (tangent * 0.9), + ))); + return { + positions: new Map(view.map(({ id, point }) => [id, fromView(point)])), + target: fromView(center), + camera: fromView([center[0], center[1], center[2] + distance]), + projected: new Map(view.map(({ id, point }) => { + const depth = distance - (point[2] - center[2]); + return [id, [(point[0] - center[0]) / (depth * tangent * aspect), (point[1] - center[1]) / (depth * tangent)]]; + })), + }; +} diff --git a/website/src/components/LibraryGraph/scene.js b/website/src/components/LibraryGraph/scene.js new file mode 100644 index 00000000..1e1356ac --- /dev/null +++ b/website/src/components/LibraryGraph/scene.js @@ -0,0 +1,225 @@ +import * as THREE from 'three'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; +import { constellationView, starDepthScale } from './model.mjs'; + +function glowTexture() { + const canvas = document.createElement('canvas'); + canvas.width = canvas.height = 64; + const context = canvas.getContext('2d'); + const gradient = context.createRadialGradient(32, 32, 0, 32, 32, 32); + for (const [offset, alpha] of [[0, 0.95], [0.12, 0.65], [0.32, 0.25], [0.65, 0.06], [1, 0]]) { + gradient.addColorStop(offset, `rgba(255,255,255,${alpha})`); + } + context.fillStyle = gradient; + context.fillRect(0, 0, 64, 64); + const texture = new THREE.CanvasTexture(canvas); + texture.generateMipmaps = false; + texture.minFilter = texture.magFilter = THREE.LinearFilter; + return texture; +} + +/** Own the renderer lifecycle independently of the docs page and its controls. */ +export function createLibraryScene(container, data, { onNode, onHover, onError }) { + const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, preserveDrawingBuffer: true }); + renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); + renderer.domElement.setAttribute('aria-label', 'Compose library 3D graph'); + container.appendChild(renderer.domElement); + const scene = new THREE.Scene(); + scene.background = new THREE.Color('#080b12'); + const camera = new THREE.PerspectiveCamera(43, 1, 0.1, 250); + const controls = new OrbitControls(camera, renderer.domElement); + controls.enableDamping = true; + controls.minDistance = 5; + controls.maxDistance = 100; + controls.maxPolarAngle = Math.PI * 0.85; + const geometry = new THREE.SphereGeometry(1, 16, 12); + const hitMaterial = new THREE.MeshBasicMaterial({ transparent: true, opacity: 0, depthWrite: false }); + const meshes = []; + const stars = []; + const edges = []; + const labels = []; + const texture = glowTexture(); + const disposables = [geometry, hitMaterial, texture]; + const byId = new Map(data.nodes.map((node) => [node.id, node])); + const backgroundGeometry = new THREE.BufferGeometry(); + const backgroundPositions = []; + const backgroundColors = []; + for (let i = 0; i < 750; i++) { + const z = 1 - 2 * (i + 0.5) / 750; + const angle = i * 2.399963; + const radius = 55 + (i % 17) * 2; + const ring = Math.sqrt(1 - z * z); + backgroundPositions.push(Math.cos(angle) * ring * radius, Math.sin(angle) * ring * radius, z * radius); + const intensity = 0.24 + (i % 7) * 0.075; + backgroundColors.push(intensity * 0.85, intensity * 0.92, intensity); + } + backgroundGeometry.setAttribute('position', new THREE.Float32BufferAttribute(backgroundPositions, 3)); + backgroundGeometry.setAttribute('color', new THREE.Float32BufferAttribute(backgroundColors, 3)); + const backgroundMaterial = new THREE.PointsMaterial({ size: 0.075, vertexColors: true, transparent: true, opacity: 0.7, depthWrite: false }); + scene.add(new THREE.Points(backgroundGeometry, backgroundMaterial)); + disposables.push(backgroundGeometry, backgroundMaterial); + for (const node of data.nodes) { + const material = new THREE.MeshBasicMaterial({ color: node.color, transparent: true, opacity: node.matches ? 1 : 0.16 }); + const mesh = new THREE.Mesh(geometry, material); + mesh.position.set(...node.position); + mesh.scale.setScalar(node.kind === 'group' ? 0.17 : 0.1); + mesh.userData.node = node; + scene.add(mesh); + const hit = new THREE.Mesh(geometry, hitMaterial); + hit.position.copy(mesh.position); + hit.scale.setScalar(0.32); + hit.userData.node = node; + scene.add(hit); + meshes.push(hit); + const glowMaterial = new THREE.SpriteMaterial({ map: texture, color: node.color, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, depthTest: false, toneMapped: false, opacity: node.matches ? 0.65 : 0.12 }); + const glow = new THREE.Sprite(glowMaterial); + glow.raycast = () => {}; + scene.add(glow); + stars.push({ node, mesh, hit, glow }); + disposables.push(material, glowMaterial); + { + const label = document.createElement(node.kind === 'group' ? 'button' : 'span'); + label.type = 'button'; + label.textContent = node.name; + label.title = node.name; + label.className = `library-graph-label${node.level === 'category' ? ' category' : ''}${node.kind === 'facet' ? ' facet' : ''}`; + label.style.setProperty('--node-color', node.color); + label.style.opacity = node.matches ? '1' : '0.35'; + if (node.kind === 'group') label.addEventListener('click', () => onNode(node)); + container.appendChild(label); + labels.push({ element: label, node, position: new THREE.Vector3(...node.position), offset: node.level === 'category' ? -30 : -23 }); + } + } + for (const edge of data.links) { + const source = byId.get(edge.source), target = byId.get(edge.target); + const lineGeometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(...source.position), new THREE.Vector3(...target.position)]); + const material = new THREE.LineBasicMaterial({ color: target.color, transparent: true, opacity: target.matches ? (target.kind === 'facet' ? 0.25 : 0.15) : 0.035 }); + scene.add(new THREE.Line(lineGeometry, material)); + edges.push({ edge, geometry: lineGeometry }); + disposables.push(lineGeometry, material); + } + let width = 1, height = 1, frame, down, hovered, selectedId, disposed = false; + function reset() { + selectedId = undefined; + hovered = undefined; + onHover(undefined); + const view = constellationView(data.nodes, camera.aspect); + for (const star of stars) { + const position = view.positions.get(star.node.id); + star.mesh.position.set(...position); + star.hit.position.set(...position); + star.glow.position.set(...position); + } + for (const label of labels) label.position.set(...view.positions.get(label.node.id)); + for (const edge of edges) { + edge.geometry.setFromPoints([new THREE.Vector3(...view.positions.get(edge.edge.source)), new THREE.Vector3(...view.positions.get(edge.edge.target))]); + edge.geometry.computeBoundingSphere(); + } + camera.position.set(...view.camera); + controls.target.set(...view.target); + controls.update(); + } + const resize = new ResizeObserver(() => { + width = container.clientWidth; + height = container.clientHeight; + if (!width || !height) return; + camera.aspect = width / height; + camera.updateProjectionMatrix(); + renderer.setSize(width, height); + reset(); + }); + resize.observe(container); + const raycaster = new THREE.Raycaster(); + const pointer = new THREE.Vector2(); + function pick(event) { + const rect = renderer.domElement.getBoundingClientRect(); + pointer.set((event.clientX - rect.left) / rect.width * 2 - 1, -(event.clientY - rect.top) / rect.height * 2 + 1); + raycaster.setFromCamera(pointer, camera); + return raycaster.intersectObjects(meshes)[0]?.object.userData.node; + } + function move(event) { + const node = pick(event); + if (node?.id !== hovered?.id) { + hovered = node; + renderer.domElement.style.cursor = node ? 'pointer' : 'grab'; + onHover(node); + } + } + function press(event) { down = [event.clientX, event.clientY]; } + function release(event) { + if (!down || Math.hypot(event.clientX - down[0], event.clientY - down[1]) > 5) return; + const node = pick(event); + down = null; + if (node) { selectedId = node.id; onNode(node); } + } + function leave() { down = null; hovered = undefined; onHover(undefined); } + function lost(event) { event.preventDefault(); onError(); } + renderer.domElement.addEventListener('pointermove', move); + renderer.domElement.addEventListener('pointerdown', press); + renderer.domElement.addEventListener('pointerup', release); + renderer.domElement.addEventListener('pointerleave', leave); + renderer.domElement.addEventListener('webglcontextlost', lost); + function animate() { + if (disposed) return; + controls.update(); + const referenceDistance = camera.position.distanceTo(controls.target); + for (const star of stars) { + const distance = star.mesh.position.distanceTo(camera.position); + const units = 2 * distance * Math.tan(43 * Math.PI / 360) / height; + const depth = starDepthScale(distance, referenceDistance); + const active = star.node.id === hovered?.id || star.node.id === selectedId; + star.mesh.scale.setScalar(units * depth * (active ? 3.1 : star.node.kind === 'facet' ? 1.65 : 2.1)); + star.mesh.material.opacity = active ? 1 : star.node.matches ? Math.min(1, 0.8 * depth) : 0.16; + star.hit.scale.setScalar(units * 10); + star.glow.scale.setScalar(units * depth * (active ? 36 : 24)); + star.glow.material.opacity = active ? 0.95 : star.node.matches ? Math.min(0.85, 0.65 * depth) : 0.12; + } + renderer.render(scene, camera); + const placed = []; + for (const label of labels) { + const projected = label.position.clone().project(camera); + const facet = label.node.kind === 'facet'; + if ((facet && label.node.id !== hovered?.id && label.node.id !== selectedId) || projected.z > 1 || Math.abs(projected.x) > 1 || Math.abs(projected.y) > 1) { + label.element.style.display = 'none'; + continue; + } + label.element.style.display = ''; + const labelWidth = Math.min(label.element.offsetWidth, width - 16); + const labelHeight = label.element.offsetHeight; + const x = Math.max(labelWidth / 2 + 8, Math.min(width - labelWidth / 2 - 8, (projected.x + 1) / 2 * width)); + const anchorY = (1 - projected.y) / 2 * height; + const offset = [label.offset, -23, 16, -42, 35, -61, 54, -80, 73, -100, 94].find((candidate) => { + const top = anchorY + candidate; + return top >= 4 && top + labelHeight <= height - 4 && !placed.some((rect) => x + labelWidth / 2 > rect.left && x - labelWidth / 2 < rect.right && top + labelHeight + 2 > rect.top && top < rect.bottom); + }); + if (offset === undefined) { label.element.style.display = 'none'; continue; } + label.offset = offset; + const y = anchorY + offset; + label.element.style.left = `${x}px`; + label.element.style.top = `${y}px`; + placed.push({ left: x - labelWidth / 2 - 3, right: x + labelWidth / 2 + 3, top: y - 2, bottom: y + labelHeight + 2 }); + } + frame = requestAnimationFrame(animate); + } + reset(); + animate(); + return { + reset, + select(id) { selectedId = id; }, + dispose() { + disposed = true; + cancelAnimationFrame(frame); + resize.disconnect(); + controls.dispose(); + renderer.domElement.removeEventListener('pointermove', move); + renderer.domElement.removeEventListener('pointerdown', press); + renderer.domElement.removeEventListener('pointerup', release); + renderer.domElement.removeEventListener('pointerleave', leave); + renderer.domElement.removeEventListener('webglcontextlost', lost); + disposables.forEach((resource) => resource.dispose()); + labels.forEach((label) => label.element.remove()); + renderer.dispose(); + renderer.domElement.remove(); + }, + }; +} diff --git a/website/src/components/LibraryGraph/styles.module.css b/website/src/components/LibraryGraph/styles.module.css new file mode 100644 index 00000000..5a650643 --- /dev/null +++ b/website/src/components/LibraryGraph/styles.module.css @@ -0,0 +1,33 @@ +.explorer { margin: 1.5rem 0 2rem; border-block: 1px solid var(--ifm-color-emphasis-300); background: var(--ifm-background-color); color: var(--ifm-font-color-base); letter-spacing: 0; } +.heading { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding-top: 1rem; } +.heading h2 { font-family: var(--ifm-heading-font-family); font-size: var(--ifm-h2-font-size, 1.5rem); font-weight: var(--ifm-heading-font-weight); line-height: 1.3; margin: 0 0 1rem; letter-spacing: 0; } +.heading span, .footer, .detail { font-size: 0.875rem; line-height: 1.5; color: var(--ifm-color-emphasis-700); } +.toolbar { display: flex; gap: 0.5rem; align-items: center; padding-bottom: 0.75rem; flex-wrap: wrap; } +.toolbar input, .toolbar select { min-height: 38px; border: 1px solid var(--ifm-color-emphasis-300); background: var(--ifm-background-color); color: inherit; border-radius: 4px; font: inherit; font-size: 0.9375rem; line-height: 1.4; padding: 0.4rem 0.55rem; min-width: 0; } +.toolbar input { flex: 1 1 150px; width: 150px; } +.toolbar select { flex: 0 1 150px; width: 150px; } +.toolbar button, .footer button { background: transparent; color: inherit; cursor: pointer; border: 1px solid var(--ifm-color-emphasis-300); border-radius: 4px; } +.iconButton { width: 38px; height: 38px; display: grid; place-items: center; flex: none; } +.viewport { height: 520px; position: relative; background: #080b12; color: #e5edf7; overflow: hidden; } +.scene { position: absolute; inset: 0 0 42px; } +.scene canvas { display: block; width: 100%; height: 100%; touch-action: none; } +.scene :global(.library-graph-label) { position: absolute; transform: translate(-50%, 0); max-width: calc(100% - 16px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: rgba(8, 11, 18, 0.72); color: var(--node-color); border: 0; border-radius: 0; padding: 2px 5px; font: inherit; font-size: 13px; line-height: 1.4; text-shadow: 0 0 8px #080b12; cursor: pointer; } +.scene :global(.library-graph-label.category) { text-transform: uppercase; font-size: 11px; font-weight: 600; } +.scene :global(.library-graph-label.facet) { pointer-events: none; color: #f0f5ff; } +.flat { width: 100%; height: calc(100% - 42px); } +.flat g[role='button'] { cursor: pointer; outline: none; } +.flat g[role='button']:focus circle:first-child { stroke: var(--ifm-font-color-base); stroke-width: 3px; } +.detail { position: absolute; bottom: 0; left: 0; right: 0; min-height: 42px; padding: 0.55rem 0.75rem; background: var(--ifm-background-color); display: flex; justify-content: flex-start; align-items: center; gap: 0.35rem 1rem; flex-wrap: wrap; pointer-events: auto; } +.detail strong { font-size: 0.9375rem; font-weight: 600; overflow-wrap: anywhere; } +.detail a { white-space: nowrap; } +.footer { padding: 0.7rem 0; display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; flex-wrap: wrap; } +.footer button { padding: 0; border: 0; font: inherit; } +.list { list-style: none; padding: 0; margin: 0 0 1rem; max-height: 300px; overflow: auto; } +.list li { display: grid; grid-template-columns: 6px minmax(0, 1fr); align-items: center; gap: 0.65rem; padding: 0.6rem 0.25rem; border-top: 1px solid var(--ifm-color-emphasis-200); font-size: 0.9375rem; line-height: 1.5; } +.list a, .list button { display: grid; grid-template-columns: minmax(0, 17rem) max-content; align-items: center; justify-content: start; gap: 0.15rem 1rem; width: 100%; min-width: 0; } +.list button { border: 0; color: inherit; background: transparent; text-align: left; padding: 0; cursor: pointer; font: inherit; } +.facetName { min-width: 0; overflow-wrap: anywhere; } +.facetStatus { color: var(--ifm-color-emphasis-700); font-size: 0.875rem; white-space: nowrap; } +.dot { width: 6px; height: 6px; border-radius: 50%; flex: none; } +[data-theme='dark'] .iconButton img, [data-theme='dark'] .footer img { filter: brightness(0) invert(0.85); } +@media (max-width: 600px) { .viewport { height: 480px; } .toolbar input { flex-basis: 100%; width: 100%; } .toolbar select { flex: 1 1 auto; width: auto; } .list button { grid-template-columns: minmax(0, 1fr); } .list a { grid-template-columns: minmax(0, 1fr) 13px; } } diff --git a/website/src/data/libraryCatalog.json b/website/src/data/libraryCatalog.json new file mode 100644 index 00000000..cdd0850a --- /dev/null +++ b/website/src/data/libraryCatalog.json @@ -0,0 +1,2771 @@ +{ + "schemaVersion": 1, + "source": { + "directories": [ + "src", + "cli/bases" + ], + "sha256": "a47058e7d9a4db9b62c3be2643be05bdc90e4042441152f3c3db71275f5c685a" + }, + "files": [ + { + "path": "src/access/AccessControl/Admin/AccessControlAdminFacet.sol", + "name": "AccessControlAdminFacet", + "kind": "facet", + "category": "access", + "family": "AccessControl", + "sha256": "b1865df3506b5aac12eaa6454dee12c1d0b4686efe0856514de8abbae8030e9c", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlAdminFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Admin/AccessControlAdminMod.sol", + "name": "AccessControlAdminMod", + "kind": "module", + "category": "access", + "family": "AccessControl", + "sha256": "811a34f3868c386a3f5b40c6f3eb24a9d23146e6e7f24f73867c3b88efde198d", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlAdminFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Batch/Grant/AccessControlGrantBatchFacet.sol", + "name": "AccessControlGrantBatchFacet", + "kind": "facet", + "category": "access", + "family": "AccessControl", + "sha256": "52a3c62a3d9c73b6603f8a9e99ed6c2295545f588b71382cbc8b81a30196d799", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlGrantBatchFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Batch/Grant/AccessControlGrantBatchMod.sol", + "name": "AccessControlGrantBatchMod", + "kind": "module", + "category": "access", + "family": "AccessControl", + "sha256": "2d5fe4aaabf19b4dcc906250f7414723111ad6c0b888ac1a232502f9bdc6177b", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlGrantBatchFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.sol", + "name": "AccessControlRevokeBatchFacet", + "kind": "facet", + "category": "access", + "family": "AccessControl", + "sha256": "bff7127fc5895ac37889e1c069b885e299fd114df04d88d57255290b1949e185", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlRevokeBatchFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchMod.sol", + "name": "AccessControlRevokeBatchMod", + "kind": "module", + "category": "access", + "family": "AccessControl", + "sha256": "216f83b594362695a270f96450fc21afeb877c470d3eb88b9f2bd617b6abe329", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlRevokeBatchFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Data/AccessControlDataFacet.sol", + "name": "AccessControlDataFacet", + "kind": "facet", + "category": "access", + "family": "AccessControl", + "sha256": "32f3c644c05a683cf615eeb7eb5881e0befd0d395ed2c5975c56c6a09cddc273", + "presets": [ + { + "id": "access-control", + "field": "required.AccessControlDataFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/access/AccessControl/Data/AccessControlDataMod.sol", + "name": "AccessControlDataMod", + "kind": "module", + "category": "access", + "family": "AccessControl", + "sha256": "7cfe4b91db41496d624c30475b0e966dd685a1152a61f5bb5d61be69936caa1c", + "presets": [ + { + "id": "access-control", + "field": "required.AccessControlDataFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/access/AccessControl/Grant/AccessControlGrantFacet.sol", + "name": "AccessControlGrantFacet", + "kind": "facet", + "category": "access", + "family": "AccessControl", + "sha256": "dd407c4cb90430c035d4ddbdfabee920a079a09d6773e71b3694c05d4d09a3ab", + "presets": [ + { + "id": "access-control", + "field": "required.AccessControlGrantFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/access/AccessControl/Grant/AccessControlGrantMod.sol", + "name": "AccessControlGrantMod", + "kind": "module", + "category": "access", + "family": "AccessControl", + "sha256": "f8fbbc976a518fde5a25b56b028e5cc86c849f2b549f975eba4e851befa7c84f", + "presets": [ + { + "id": "access-control", + "field": "required.AccessControlGrantFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/access/AccessControl/Pausable/AccessControlPausableFacet.sol", + "name": "AccessControlPausableFacet", + "kind": "facet", + "category": "access", + "family": "AccessControl", + "sha256": "67a87e3775523c8d5a410053f209182424bc8704271795a677044abf147a3218", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlPausableFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Pausable/AccessControlPausableMod.sol", + "name": "AccessControlPausableMod", + "kind": "module", + "category": "access", + "family": "AccessControl", + "sha256": "aa5de5476e2807a19f6df16c35c5d608506b61a212c611541061ab94f6ed1212", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlPausableFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Renounce/AccessControlRenounceFacet.sol", + "name": "AccessControlRenounceFacet", + "kind": "facet", + "category": "access", + "family": "AccessControl", + "sha256": "1d62dfe83a1958ca9fcdc6d65ca11b02beb244586f7e303ad78f99cff1a8f0a1", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlRenounceFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Renounce/AccessControlRenounceMod.sol", + "name": "AccessControlRenounceMod", + "kind": "module", + "category": "access", + "family": "AccessControl", + "sha256": "b1eeeadb4ae8a932ec5148f2f9bd8a334618efd9167e6d948d4f72ddf3fed4e0", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlRenounceFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Revoke/AccessControlRevokeFacet.sol", + "name": "AccessControlRevokeFacet", + "kind": "facet", + "category": "access", + "family": "AccessControl", + "sha256": "c850334e7660b312cb88c03f5f88bfb0c1eb2575f531d76bdb7c38f4199a888b", + "presets": [ + { + "id": "access-control", + "field": "required.AccessControlRevokeFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/access/AccessControl/Revoke/AccessControlRevokeMod.sol", + "name": "AccessControlRevokeMod", + "kind": "module", + "category": "access", + "family": "AccessControl", + "sha256": "ec6eba64a8b31c155c5fec25d0c9b4d74340953f98505a7fe3ed0ad5a8c47e4d", + "presets": [ + { + "id": "access-control", + "field": "required.AccessControlRevokeFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.sol", + "name": "AccessControlTemporalDataFacet", + "kind": "facet", + "category": "access", + "family": "AccessControl", + "sha256": "9071c9898442e5bd3343247442dd63cdd84fa3fb4e2258ac93354b166659f99a", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlTemporalDataFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataMod.sol", + "name": "AccessControlTemporalDataMod", + "kind": "module", + "category": "access", + "family": "AccessControl", + "sha256": "ff209280707af8af62a061f030eb8b873d13c126f7bb73b0a47736d699e797f9", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlTemporalDataFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.sol", + "name": "AccessControlTemporalGrantFacet", + "kind": "facet", + "category": "access", + "family": "AccessControl", + "sha256": "7d73f8efa3521c6ec3452ff3c75751aac443c7daf87cbc59320a530bd233ebab", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlTemporalGrantFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantMod.sol", + "name": "AccessControlTemporalGrantMod", + "kind": "module", + "category": "access", + "family": "AccessControl", + "sha256": "af5756514f5f14ac1c0a3851779546f60de565e48a810162d4a37cc873bc1dc7", + "presets": [ + { + "id": "access-control", + "field": "optional.AccessControlTemporalGrantFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/access/Owner/Data/OwnerDataFacet.sol", + "name": "OwnerDataFacet", + "kind": "facet", + "category": "access", + "family": "Owner", + "sha256": "bed2e7824e6e7998e22503d441bab7619df87080fad84f041acdb4959ebfb760", + "presets": [ + { + "id": "owner", + "field": "required.OwnerDataFacet.path", + "role": "required" + }, + { + "id": "owner-two-step", + "field": "required.OwnerDataFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/access/Owner/Data/OwnerDataMod.sol", + "name": "OwnerDataMod", + "kind": "module", + "category": "access", + "family": "Owner", + "sha256": "0075c032f104fc73ae2d64533a140407878234f00bcf8deb025607216c0eca0c", + "presets": [ + { + "id": "owner", + "field": "required.OwnerDataFacet.mod", + "role": "required" + }, + { + "id": "owner-two-step", + "field": "required.OwnerDataFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/access/Owner/Renounce/OwnerRenounceFacet.sol", + "name": "OwnerRenounceFacet", + "kind": "facet", + "category": "access", + "family": "Owner", + "sha256": "956afac8c7a0b846bf311bac262f922f5f58b1e42c7329d77fa243bbf100dfb2", + "presets": [ + { + "id": "owner", + "field": "optional.OwnerRenounceFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/access/Owner/Renounce/OwnerRenounceMod.sol", + "name": "OwnerRenounceMod", + "kind": "module", + "category": "access", + "family": "Owner", + "sha256": "67302ec7a76268d2997e1e5f01210adc3556c8179a3812929ff7c5f1f7628060", + "presets": [ + { + "id": "owner", + "field": "optional.OwnerRenounceFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/access/Owner/Transfer/OwnerTransferFacet.sol", + "name": "OwnerTransferFacet", + "kind": "facet", + "category": "access", + "family": "Owner", + "sha256": "8789256ca46bb600622cb70a047657e3e2acc97761aa4ccb817886173037fb3d", + "presets": [ + { + "id": "owner", + "field": "required.OwnerTransferFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/access/Owner/Transfer/OwnerTransferMod.sol", + "name": "OwnerTransferMod", + "kind": "module", + "category": "access", + "family": "Owner", + "sha256": "4f892916c134376c3d6c8df315497944dd08dcbbd161113d46dfaf5387afe55c", + "presets": [ + { + "id": "owner", + "field": "required.OwnerTransferFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.sol", + "name": "OwnerTwoStepDataFacet", + "kind": "facet", + "category": "access", + "family": "Owner", + "sha256": "993a32d4b2d00017b62c484ea687bd9e9b31b57f07fab96eb9849eeb00985af7", + "presets": [ + { + "id": "owner-two-step", + "field": "required.OwnerTwoStepDataFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/access/Owner/TwoSteps/Data/OwnerTwoStepDataMod.sol", + "name": "OwnerTwoStepDataMod", + "kind": "module", + "category": "access", + "family": "Owner", + "sha256": "7f27e6f17c8505d3978586482200108009b7ea3bda9e5aa6946c24f20056760c", + "presets": [ + { + "id": "owner-two-step", + "field": "required.OwnerTwoStepDataFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceFacet.sol", + "name": "OwnerTwoStepRenounceFacet", + "kind": "facet", + "category": "access", + "family": "Owner", + "sha256": "dd4654a846c1f7c338c59a18a42eba05f2fac631edb3b043cff56e8f9932ce66", + "presets": [ + { + "id": "owner-two-step", + "field": "optional.OwnerTwoStepRenounceFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceMod.sol", + "name": "OwnerTwoStepRenounceMod", + "kind": "module", + "category": "access", + "family": "Owner", + "sha256": "4472732ae965d132cb3bb5384a5d37abeaf513e635827e3c13c528f241b8211b", + "presets": [ + { + "id": "owner-two-step", + "field": "optional.OwnerTwoStepRenounceFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferFacet.sol", + "name": "OwnerTwoStepTransferFacet", + "kind": "facet", + "category": "access", + "family": "Owner", + "sha256": "ff8fbff2b59e9cf3ed50242f63b8ab9404e927135d335433fc7185f94aa07da3", + "presets": [ + { + "id": "owner-two-step", + "field": "required.OwnerTwoStepTransferFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferMod.sol", + "name": "OwnerTwoStepTransferMod", + "kind": "module", + "category": "access", + "family": "Owner", + "sha256": "8da4e777347aa824c5de9c5c67b8fcb025e132431e83f50a6566d762d9c1657b", + "presets": [ + { + "id": "owner-two-step", + "field": "required.OwnerTwoStepTransferFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/diamond/DiamondInspectFacet.sol", + "name": "DiamondInspectFacet", + "kind": "facet", + "category": "diamond", + "family": null, + "sha256": "d8a41156002d47ac015485467d865ae5df059a443bfdedcfd65422a1673206b3", + "presets": [ + { + "id": "diamond", + "field": "required.DiamondInspectFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/diamond/DiamondMod.sol", + "name": "DiamondMod", + "kind": "module", + "category": "diamond", + "family": null, + "sha256": "23bdff8c0660ff2d6f1e434ec69e5f736400eb870a0e57d2eff688169e2160a5", + "presets": [] + }, + { + "path": "src/diamond/DiamondUpgradeFacet.sol", + "name": "DiamondUpgradeFacet", + "kind": "facet", + "category": "diamond", + "family": null, + "sha256": "db81deb8a5c30575da9f579507db684ac35f8af0c9b30e10176ea13f5fb0505a", + "presets": [ + { + "id": "diamond", + "field": "optional.DiamondUpgradeFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/diamond/DiamondUpgradeMod.sol", + "name": "DiamondUpgradeMod", + "kind": "module", + "category": "diamond", + "family": null, + "sha256": "b561e2b76d53ea9e96546761b1d18e8ab700fa0068f822d3c5fd2a54aa5a9afd", + "presets": [] + }, + { + "path": "src/diamond/example/ExampleDiamond.sol", + "name": "ExampleDiamond", + "kind": "other", + "category": "diamond", + "family": "example", + "sha256": "6e9c5224fc427a8226a2f74c845302af444f937a04fa7b735ba63b537ef858a5", + "presets": [] + }, + { + "path": "src/interfaceDetection/ERC165/ERC165Facet.sol", + "name": "ERC165Facet", + "kind": "facet", + "category": "interfaceDetection", + "family": "ERC165", + "sha256": "6dd84649d6d342246c46eda8f7b4b12447285badd4fae9f46f7f5868dc4f95aa", + "presets": [ + { + "id": "libraries", + "field": "optional.ERC165Facet.path", + "role": "optional" + } + ] + }, + { + "path": "src/interfaceDetection/ERC165/ERC165Mod.sol", + "name": "ERC165Mod", + "kind": "module", + "category": "interfaceDetection", + "family": "ERC165", + "sha256": "5d94ccd1a866a72e938a4cff62061e1691f7995f520a77ed363a02d4a88b4808", + "presets": [] + }, + { + "path": "src/interfaces/IERC1155.sol", + "name": "IERC1155", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "34095796fdeb3fb6253e153ec3051c0ac805a8df8a7a210bc789baee340d3b00", + "presets": [ + { + "id": "erc-1155", + "field": "erc165.path", + "role": null + } + ] + }, + { + "path": "src/interfaces/IERC1155Metadata_URI.sol", + "name": "IERC1155Metadata_URI", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "3add6c3a671c148549cefe3ed7c5c5903b314557be7f6c3e7ec9608d5f31603f", + "presets": [ + { + "id": "erc-1155", + "field": "optional.ERC1155MetadataFacet.erc165.path", + "role": "optional" + } + ] + }, + { + "path": "src/interfaces/IERC1155Receiver.sol", + "name": "IERC1155Receiver", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "d5d731e52ab1fc83c6fd953c7eeeca8a3afdf254d2959de2d25fdb56a670face", + "presets": [] + }, + { + "path": "src/interfaces/IERC173.sol", + "name": "IERC173", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "e749fa585f22799647d1d14e3879d86c24c8663f0b2a403eb87372797ba5b17f", + "presets": [ + { + "id": "owner", + "field": "erc165.path", + "role": null + } + ] + }, + { + "path": "src/interfaces/IERC20.sol", + "name": "IERC20", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "617f384e099eee7b5174f41ce487de006e1e9a467f69738e7a9cd09270d7e589", + "presets": [ + { + "id": "erc-20", + "field": "erc165.path", + "role": null + } + ] + }, + { + "path": "src/interfaces/IERC2981.sol", + "name": "IERC2981", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "4ab8f2c941a0e42bfa137301fe9e15be1c9259bbda9ba9793560d7d56bf2c16d", + "presets": [ + { + "id": "erc-1155", + "field": "optional.RoyaltyFacet.erc165.path", + "role": "optional" + }, + { + "id": "erc-6909", + "field": "optional.RoyaltyFacet.erc165.path", + "role": "optional" + }, + { + "id": "erc-721", + "field": "optional.RoyaltyFacet.erc165.path", + "role": "optional" + }, + { + "id": "erc-721-enumerable", + "field": "optional.RoyaltyFacet.erc165.path", + "role": "optional" + } + ] + }, + { + "path": "src/interfaces/IERC6909.sol", + "name": "IERC6909", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "918a0bffcbed43d1dc7f95f9f6223337f861d3fd368201090a0b1637a369c7b4", + "presets": [ + { + "id": "erc-6909", + "field": "erc165.path", + "role": null + } + ] + }, + { + "path": "src/interfaces/IERC721.sol", + "name": "IERC721", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "f891f08d58a8cb508ee172ba48912606078ea99731f29e3ee3df6653e6a1840a", + "presets": [ + { + "id": "erc-721", + "field": "erc165.path", + "role": null + }, + { + "id": "erc-721-enumerable", + "field": "erc165.path", + "role": null + } + ] + }, + { + "path": "src/interfaces/IERC721Enumerable.sol", + "name": "IERC721Enumerable", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "5974309e0f25d19e31169fb2969d96582cf3eb310cbaf5c72994e4497286644c", + "presets": [] + }, + { + "path": "src/interfaces/IERC721Metadata.sol", + "name": "IERC721Metadata", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "143d656919d1d9150dbe92e557ff77a2a30bbb802c79363f7c392f69221f952f", + "presets": [ + { + "id": "erc-721", + "field": "optional.ERC721MetadataFacet.erc165.path", + "role": "optional" + }, + { + "id": "erc-721-enumerable", + "field": "optional.ERC721MetadataFacet.erc165.path", + "role": "optional" + } + ] + }, + { + "path": "src/interfaces/IERC721Receiver.sol", + "name": "IERC721Receiver", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "04f34d64ebd96f727f220851f96056379cdc37aeb029c7f1d5611b3e6d0e3b85", + "presets": [] + }, + { + "path": "src/interfaces/IFacet.sol", + "name": "IFacet", + "kind": "interface", + "category": "interfaces", + "family": null, + "sha256": "570d5128e3ee269a7bcdcb513d4cda3e35f7808a509f884cd175d20044efeb6d", + "presets": [] + }, + { + "path": "src/libraries/NonReentrancyMod.sol", + "name": "NonReentrancyMod", + "kind": "module", + "category": "libraries", + "family": null, + "sha256": "1d7a95e2f001970eb3367da10596dbe8f91d653776b28c05e52a2b7abe83b1b4", + "presets": [] + }, + { + "path": "src/libraries/Utils.sol", + "name": "Utils", + "kind": "other", + "category": "libraries", + "family": null, + "sha256": "fd7932511bbbfa1ba872374f9efeeefaed021ff745f3432f49edd78351b09da8", + "presets": [] + }, + { + "path": "src/token/ERC1155/Approve/ERC1155ApproveFacet.sol", + "name": "ERC1155ApproveFacet", + "kind": "facet", + "category": "token", + "family": "ERC1155", + "sha256": "2e587e6b065cd593eaf9d157e4079f864d3c6979164d4e9f49b9b547791de67f", + "presets": [ + { + "id": "erc-1155", + "field": "required.ERC1155ApproveFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC1155/Approve/ERC1155ApproveMod.sol", + "name": "ERC1155ApproveMod", + "kind": "module", + "category": "token", + "family": "ERC1155", + "sha256": "8978c630504d0065909279b616c24bc556c261431ca86294618fe0c27f838668", + "presets": [ + { + "id": "erc-1155", + "field": "required.ERC1155ApproveFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC1155/Burn/ERC1155BurnFacet.sol", + "name": "ERC1155BurnFacet", + "kind": "facet", + "category": "token", + "family": "ERC1155", + "sha256": "0ceb8dba848b8a606d72cece0ce9c62cc89182c4f7b265c72089c562d07d5102", + "presets": [ + { + "id": "erc-1155", + "field": "optional.ERC1155BurnFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC1155/Burn/ERC1155BurnMod.sol", + "name": "ERC1155BurnMod", + "kind": "module", + "category": "token", + "family": "ERC1155", + "sha256": "6df6f05d85518661d163b40147ef63719f22e10ba41cca1ac9a33163d05bd17e", + "presets": [ + { + "id": "erc-1155", + "field": "optional.ERC1155BurnFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC1155/Data/ERC1155DataFacet.sol", + "name": "ERC1155DataFacet", + "kind": "facet", + "category": "token", + "family": "ERC1155", + "sha256": "450404d05c441e301f8eb85ac7ff5ade28862d83f8bb68546ae80b2ee9670b9f", + "presets": [ + { + "id": "erc-1155", + "field": "required.ERC1155DataFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC1155/Metadata/ERC1155MetadataFacet.sol", + "name": "ERC1155MetadataFacet", + "kind": "facet", + "category": "token", + "family": "ERC1155", + "sha256": "23b6a9e0333e34dfdbb1c459d1869bb56364487a91b3e93849388f38e9c6f88a", + "presets": [ + { + "id": "erc-1155", + "field": "optional.ERC1155MetadataFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC1155/Metadata/ERC1155MetadataMod.sol", + "name": "ERC1155MetadataMod", + "kind": "module", + "category": "token", + "family": "ERC1155", + "sha256": "a2d6852007a1b2e0afbc4a432a7289be30d61d8fe7fbb29765091e9af7772ddd", + "presets": [ + { + "id": "erc-1155", + "field": "optional.ERC1155MetadataFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC1155/Mint/ERC1155MintMod.sol", + "name": "ERC1155MintMod", + "kind": "module", + "category": "token", + "family": "ERC1155", + "sha256": "1e70927f96288f042d4aac61708d12c796936acce543f4fe38751f501b839ff9", + "presets": [] + }, + { + "path": "src/token/ERC1155/Transfer/ERC1155TransferFacet.sol", + "name": "ERC1155TransferFacet", + "kind": "facet", + "category": "token", + "family": "ERC1155", + "sha256": "e243c64be551b2998e3bd5fddd23c3081f0ac7e521fa7e22de8e3486ef0b3ccc", + "presets": [ + { + "id": "erc-1155", + "field": "required.ERC1155TransferFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC1155/Transfer/ERC1155TransferMod.sol", + "name": "ERC1155TransferMod", + "kind": "module", + "category": "token", + "family": "ERC1155", + "sha256": "719098b19453421adec038ae11121eb9def06288ed20f6f736629363be09e959", + "presets": [ + { + "id": "erc-1155", + "field": "required.ERC1155TransferFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC20/Approve/ERC20ApproveFacet.sol", + "name": "ERC20ApproveFacet", + "kind": "facet", + "category": "token", + "family": "ERC20", + "sha256": "d1d99444d39e27750b243cbb55773559d2b95d3755817fe23a80c0f06a597bb9", + "presets": [ + { + "id": "erc-20", + "field": "required.ERC20ApproveFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC20/Approve/ERC20ApproveMod.sol", + "name": "ERC20ApproveMod", + "kind": "module", + "category": "token", + "family": "ERC20", + "sha256": "12736e66eccaf7be9553f144d24b661df6d4537a0e1a0b5495a3462531606ddb", + "presets": [] + }, + { + "path": "src/token/ERC20/Bridgeable/ERC20BridgeableFacet.sol", + "name": "ERC20BridgeableFacet", + "kind": "facet", + "category": "token", + "family": "ERC20", + "sha256": "50769b7d831548e32960ed1d7e1ddc1591d7c202e93426d685f0b3a5425f80ba", + "presets": [ + { + "id": "erc-20", + "field": "optional.ERC20BridgeableFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC20/Bridgeable/ERC20BridgeableMod.sol", + "name": "ERC20BridgeableMod", + "kind": "module", + "category": "token", + "family": "ERC20", + "sha256": "c2428169b8d42b3aa97fcf9a304d3d15c956aedc28bd326214fcb9eeffb673d2", + "presets": [] + }, + { + "path": "src/token/ERC20/Burn/ERC20BurnFacet.sol", + "name": "ERC20BurnFacet", + "kind": "facet", + "category": "token", + "family": "ERC20", + "sha256": "5819be14fdb1aa0dccfe5930d467fd82798e866b95fa596d9c624867ba4bfaeb", + "presets": [ + { + "id": "erc-20", + "field": "optional.ERC20BurnFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC20/Burn/ERC20BurnMod.sol", + "name": "ERC20BurnMod", + "kind": "module", + "category": "token", + "family": "ERC20", + "sha256": "3e327e58d224e2c0b751125bf8a23833794e831dd7e77e6826c6eadb3d7dba4d", + "presets": [] + }, + { + "path": "src/token/ERC20/Data/ERC20DataFacet.sol", + "name": "ERC20DataFacet", + "kind": "facet", + "category": "token", + "family": "ERC20", + "sha256": "59f8c9100807c5b06faa90b1b45758a1321efd41ca6245a4b3347527803c8da9", + "presets": [ + { + "id": "erc-20", + "field": "required.ERC20DataFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC20/Metadata/ERC20MetadataFacet.sol", + "name": "ERC20MetadataFacet", + "kind": "facet", + "category": "token", + "family": "ERC20", + "sha256": "0a199c7e50d710340c506f3fb95b798edd749b04aac7fc350d900ea5c7ee2d5d", + "presets": [ + { + "id": "erc-20", + "field": "optional.ERC20MetadataFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC20/Metadata/ERC20MetadataMod.sol", + "name": "ERC20MetadataMod", + "kind": "module", + "category": "token", + "family": "ERC20", + "sha256": "5bb1980f8ffb77d5e487c7dc23bd149830a09246fa2198e2215f0ed28e2f7825", + "presets": [ + { + "id": "erc-20", + "field": "optional.ERC20MetadataFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC20/Mint/ERC20MintMod.sol", + "name": "ERC20MintMod", + "kind": "module", + "category": "token", + "family": "ERC20", + "sha256": "e51573fb880b8999d4968ae6dc09327e308bdda5b8a018cd8d8ce6766defac4e", + "presets": [] + }, + { + "path": "src/token/ERC20/Permit/ERC20PermitFacet.sol", + "name": "ERC20PermitFacet", + "kind": "facet", + "category": "token", + "family": "ERC20", + "sha256": "910e0450153e8861f5944b54a203cd2aa217d6cd5be15da571923950cd450696", + "presets": [ + { + "id": "erc-20", + "field": "optional.ERC20PermitFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC20/Permit/ERC20PermitMod.sol", + "name": "ERC20PermitMod", + "kind": "module", + "category": "token", + "family": "ERC20", + "sha256": "99e8c2be4fcacc49b9ff1978de67010a8cb001d742e2c255b1aa5074ca77ebf9", + "presets": [] + }, + { + "path": "src/token/ERC20/Transfer/ERC20TransferFacet.sol", + "name": "ERC20TransferFacet", + "kind": "facet", + "category": "token", + "family": "ERC20", + "sha256": "3e6c4f419380842daf1f3849d7f72b0d688cccb5dd695fff5a443232df16642d", + "presets": [ + { + "id": "erc-20", + "field": "required.ERC20TransferFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC20/Transfer/ERC20TransferMod.sol", + "name": "ERC20TransferMod", + "kind": "module", + "category": "token", + "family": "ERC20", + "sha256": "e97918e7ce9a8c3dcc17561a64fe9171c7c6dd16d9cfd83759d907f79f15840b", + "presets": [] + }, + { + "path": "src/token/ERC6909/Approve/ERC6909ApproveFacet.sol", + "name": "ERC6909ApproveFacet", + "kind": "facet", + "category": "token", + "family": "ERC6909", + "sha256": "e9d6789282601aa0c7c6c390d68a683896d17ef59fa22cdd2c18459ddff28633", + "presets": [ + { + "id": "erc-6909", + "field": "required.ERC6909ApproveFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC6909/Approve/ERC6909ApproveMod.sol", + "name": "ERC6909ApproveMod", + "kind": "module", + "category": "token", + "family": "ERC6909", + "sha256": "8d34276f9a8ce25533f9d8474cede98e8507407ff679c53b6b74575294d76381", + "presets": [ + { + "id": "erc-6909", + "field": "required.ERC6909ApproveFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC6909/Burn/ERC6909BurnFacet.sol", + "name": "ERC6909BurnFacet", + "kind": "facet", + "category": "token", + "family": "ERC6909", + "sha256": "01332a28e7a7b352f60bdc50d2dd840ac2b4e2473140fa0057bed321b2bec28c", + "presets": [ + { + "id": "erc-6909", + "field": "optional.ERC6909BurnFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC6909/Burn/ERC6909BurnMod.sol", + "name": "ERC6909BurnMod", + "kind": "module", + "category": "token", + "family": "ERC6909", + "sha256": "cb9eb62cde4a12f9999d7cf9533e784d725579ed81252678b428bc9c62ff3665", + "presets": [ + { + "id": "erc-6909", + "field": "optional.ERC6909BurnFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC6909/Data/ERC6909DataFacet.sol", + "name": "ERC6909DataFacet", + "kind": "facet", + "category": "token", + "family": "ERC6909", + "sha256": "65e200878bf64f3b9d9269a0d52f1a2f31ca92dd0119ec85551db786f87a9304", + "presets": [ + { + "id": "erc-6909", + "field": "required.ERC6909DataFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC6909/Mint/ERC6909MintMod.sol", + "name": "ERC6909MintMod", + "kind": "module", + "category": "token", + "family": "ERC6909", + "sha256": "81c756fa7b87c5ac9719f6642d917cf5388bc5352cf9cff8c03fbf52204d2d52", + "presets": [] + }, + { + "path": "src/token/ERC6909/Operator/ERC6909OperatorFacet.sol", + "name": "ERC6909OperatorFacet", + "kind": "facet", + "category": "token", + "family": "ERC6909", + "sha256": "9a6bcf52e67ea428a2616fd61896489798415091ccecfa54260d704c37e892ea", + "presets": [ + { + "id": "erc-6909", + "field": "required.ERC6909OperatorFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC6909/Operator/ERC6909OperatorMod.sol", + "name": "ERC6909OperatorMod", + "kind": "module", + "category": "token", + "family": "ERC6909", + "sha256": "68b14c4cb5c7e23120d15e00564c11b407290ff1243ff483868ea2f166d0d72b", + "presets": [ + { + "id": "erc-6909", + "field": "required.ERC6909OperatorFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC6909/Transfer/ERC6909TransferFacet.sol", + "name": "ERC6909TransferFacet", + "kind": "facet", + "category": "token", + "family": "ERC6909", + "sha256": "17276ed5cb7addbca42a4214363232252028a0a709db729dd82bfd0981b23644", + "presets": [ + { + "id": "erc-6909", + "field": "required.ERC6909TransferFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC6909/Transfer/ERC6909TransferMod.sol", + "name": "ERC6909TransferMod", + "kind": "module", + "category": "token", + "family": "ERC6909", + "sha256": "566a835d35bcad6c8a19ffafdda58801c5ecf0b216f6c713193371b4b4c9399d", + "presets": [ + { + "id": "erc-6909", + "field": "required.ERC6909TransferFacet.mod", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC721/Approve/ERC721ApproveFacet.sol", + "name": "ERC721ApproveFacet", + "kind": "facet", + "category": "token", + "family": "ERC721", + "sha256": "e829ed22d9ac8c991371c4fd3ec7af5f8c9c93c14e4a1b20c91072c77ae48f46", + "presets": [ + { + "id": "erc-721", + "field": "required.ERC721ApproveFacet.path", + "role": "required" + }, + { + "id": "erc-721-enumerable", + "field": "required.ERC721ApproveFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC721/Approve/ERC721ApproveMod.sol", + "name": "ERC721ApproveMod", + "kind": "module", + "category": "token", + "family": "ERC721", + "sha256": "6fea4e4f2acb428b452a8bfa3bc18dc48a1106fab06301ceeada74f61fe6b8d3", + "presets": [] + }, + { + "path": "src/token/ERC721/Burn/ERC721BurnFacet.sol", + "name": "ERC721BurnFacet", + "kind": "facet", + "category": "token", + "family": "ERC721", + "sha256": "bda42d4703382c04fa68428a442ac0c8aae9aaf4dc5d22a8307fe50cbe6e5348", + "presets": [ + { + "id": "erc-721", + "field": "optional.ERC721BurnFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC721/Burn/ERC721BurnMod.sol", + "name": "ERC721BurnMod", + "kind": "module", + "category": "token", + "family": "ERC721", + "sha256": "d27109c9ffe20b76c7dfde8e7e196f8d23137bef27e54e43034e9bdd05a0e181", + "presets": [] + }, + { + "path": "src/token/ERC721/Data/ERC721DataFacet.sol", + "name": "ERC721DataFacet", + "kind": "facet", + "category": "token", + "family": "ERC721", + "sha256": "5af762a7f21ce40c6576fccc6747dc677c3cdd851c979ebb69140e6c5559c686", + "presets": [ + { + "id": "erc-721", + "field": "required.ERC721DataFacet.path", + "role": "required" + }, + { + "id": "erc-721-enumerable", + "field": "required.ERC721DataFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnFacet.sol", + "name": "ERC721EnumerableBurnFacet", + "kind": "facet", + "category": "token", + "family": "ERC721", + "sha256": "c4333e63a755235133fe53b1215f042fd0441e6ebb7ed1ba796ebb2266a6744c", + "presets": [ + { + "id": "erc-721-enumerable", + "field": "optional.ERC721EnumerableBurnFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnMod.sol", + "name": "ERC721EnumerableBurnMod", + "kind": "module", + "category": "token", + "family": "ERC721", + "sha256": "d907811ac83dc607b12d15125a82807717fcdcdf6b7c0b8126ae8c07f12a57fc", + "presets": [] + }, + { + "path": "src/token/ERC721/Enumerable/Data/ERC721EnumerableDataFacet.sol", + "name": "ERC721EnumerableDataFacet", + "kind": "facet", + "category": "token", + "family": "ERC721", + "sha256": "7813676b850b03975b27339b85c276420fb74980dbda9819f6afae05d4d2d6da", + "presets": [ + { + "id": "erc-721-enumerable", + "field": "required.ERC721EnumerableDataFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC721/Enumerable/Mint/ERC721EnumerableMintMod.sol", + "name": "ERC721EnumerableMintMod", + "kind": "module", + "category": "token", + "family": "ERC721", + "sha256": "368795d800f18cccb3aac43b359dfbcd7389af018f23a836bbf0f1370fda98bc", + "presets": [] + }, + { + "path": "src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.sol", + "name": "ERC721EnumerableTransferFacet", + "kind": "facet", + "category": "token", + "family": "ERC721", + "sha256": "3c0253477ed826d4f4b2e174b9009b2f5cab6685e37c3e6c2d4f03937c565c5e", + "presets": [ + { + "id": "erc-721-enumerable", + "field": "required.ERC721EnumerableTransferFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferMod.sol", + "name": "ERC721EnumerableTransferMod", + "kind": "module", + "category": "token", + "family": "ERC721", + "sha256": "75697500a1822a5efda31e9dca55b0ce75cf4713d5a7c82b18dd63fc80fc9ed2", + "presets": [] + }, + { + "path": "src/token/ERC721/Metadata/ERC721MetadataFacet.sol", + "name": "ERC721MetadataFacet", + "kind": "facet", + "category": "token", + "family": "ERC721", + "sha256": "f9c867dd49408c8aae89e62d17eafceef1f9b81b9c411272e895325954374974", + "presets": [ + { + "id": "erc-721", + "field": "optional.ERC721MetadataFacet.path", + "role": "optional" + }, + { + "id": "erc-721-enumerable", + "field": "optional.ERC721MetadataFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC721/Metadata/ERC721MetadataMod.sol", + "name": "ERC721MetadataMod", + "kind": "module", + "category": "token", + "family": "ERC721", + "sha256": "e0de3239f4b143333e10ad7c56faa84a867e2fca69661f9c4ca6c82eb86ee69e", + "presets": [ + { + "id": "erc-721", + "field": "optional.ERC721MetadataFacet.mod", + "role": "optional" + }, + { + "id": "erc-721-enumerable", + "field": "optional.ERC721MetadataFacet.mod", + "role": "optional" + } + ] + }, + { + "path": "src/token/ERC721/Mint/ERC721MintMod.sol", + "name": "ERC721MintMod", + "kind": "module", + "category": "token", + "family": "ERC721", + "sha256": "2bbaa4a50dbda6f3ef9818a135c4548d84197518354da2222c86f772f4cf405d", + "presets": [] + }, + { + "path": "src/token/ERC721/Transfer/ERC721TransferFacet.sol", + "name": "ERC721TransferFacet", + "kind": "facet", + "category": "token", + "family": "ERC721", + "sha256": "9069a80d777e4e3e3cd1730cb899cbfd8f3bb99b69b8a52876a40a5cac162f62", + "presets": [ + { + "id": "erc-721", + "field": "required.ERC721TransferFacet.path", + "role": "required" + } + ] + }, + { + "path": "src/token/ERC721/Transfer/ERC721TransferMod.sol", + "name": "ERC721TransferMod", + "kind": "module", + "category": "token", + "family": "ERC721", + "sha256": "9000bff38e2d5e7d4a51fdd949c0437cb94a6ce9db8077f757c082657a9a1cb5", + "presets": [] + }, + { + "path": "src/token/Royalty/RoyaltyFacet.sol", + "name": "RoyaltyFacet", + "kind": "facet", + "category": "token", + "family": "Royalty", + "sha256": "fa8a6f973ece8f4ab2b35bd24b6e443af08bb582c1ba83e8eb693074ebeda73b", + "presets": [ + { + "id": "erc-1155", + "field": "optional.RoyaltyFacet.path", + "role": "optional" + }, + { + "id": "erc-6909", + "field": "optional.RoyaltyFacet.path", + "role": "optional" + }, + { + "id": "erc-721", + "field": "optional.RoyaltyFacet.path", + "role": "optional" + }, + { + "id": "erc-721-enumerable", + "field": "optional.RoyaltyFacet.path", + "role": "optional" + } + ] + }, + { + "path": "src/token/Royalty/RoyaltyMod.sol", + "name": "RoyaltyMod", + "kind": "module", + "category": "token", + "family": "Royalty", + "sha256": "48ff996ec943cb17b3362ff336c996be33ad83d00ac7a5d3ea321e03709ad59c", + "presets": [ + { + "id": "erc-1155", + "field": "optional.RoyaltyFacet.mod", + "role": "optional" + }, + { + "id": "erc-6909", + "field": "optional.RoyaltyFacet.mod", + "role": "optional" + }, + { + "id": "erc-721", + "field": "optional.RoyaltyFacet.mod", + "role": "optional" + }, + { + "id": "erc-721-enumerable", + "field": "optional.RoyaltyFacet.mod", + "role": "optional" + } + ] + } + ], + "presets": [ + { + "id": "access-control", + "sourcePath": "cli/bases/accesscontrol.json", + "definition": { + "label": "AccessControl", + "order": 9, + "access": true, + "accessType": "roles", + "required": { + "AccessControlDataFacet": { + "path": "@perfect-abstractions/compose/access/AccessControl/Data/AccessControlDataFacet.sol", + "mod": "@perfect-abstractions/compose/access/AccessControl/Data/AccessControlDataMod.sol" + }, + "AccessControlGrantFacet": { + "path": "@perfect-abstractions/compose/access/AccessControl/Grant/AccessControlGrantFacet.sol", + "mod": "@perfect-abstractions/compose/access/AccessControl/Grant/AccessControlGrantMod.sol" + }, + "AccessControlRevokeFacet": { + "path": "@perfect-abstractions/compose/access/AccessControl/Revoke/AccessControlRevokeFacet.sol", + "mod": "@perfect-abstractions/compose/access/AccessControl/Revoke/AccessControlRevokeMod.sol" + } + }, + "optional": { + "AccessControlAdminFacet": { + "path": "@perfect-abstractions/compose/access/AccessControl/Admin/AccessControlAdminFacet.sol", + "mod": "@perfect-abstractions/compose/access/AccessControl/Admin/AccessControlAdminMod.sol" + }, + "AccessControlGrantBatchFacet": { + "path": "@perfect-abstractions/compose/access/AccessControl/Batch/Grant/AccessControlGrantBatchFacet.sol", + "mod": "@perfect-abstractions/compose/access/AccessControl/Batch/Grant/AccessControlGrantBatchMod.sol" + }, + "AccessControlRevokeBatchFacet": { + "path": "@perfect-abstractions/compose/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.sol", + "mod": "@perfect-abstractions/compose/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchMod.sol" + }, + "AccessControlPausableFacet": { + "path": "@perfect-abstractions/compose/access/AccessControl/Pausable/AccessControlPausableFacet.sol", + "mod": "@perfect-abstractions/compose/access/AccessControl/Pausable/AccessControlPausableMod.sol" + }, + "AccessControlRenounceFacet": { + "path": "@perfect-abstractions/compose/access/AccessControl/Renounce/AccessControlRenounceFacet.sol", + "mod": "@perfect-abstractions/compose/access/AccessControl/Renounce/AccessControlRenounceMod.sol" + }, + "AccessControlTemporalDataFacet": { + "path": "@perfect-abstractions/compose/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.sol", + "mod": "@perfect-abstractions/compose/access/AccessControl/Temporal/Data/AccessControlTemporalDataMod.sol" + }, + "AccessControlTemporalGrantFacet": { + "path": "@perfect-abstractions/compose/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.sol", + "mod": "@perfect-abstractions/compose/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantMod.sol" + } + } + }, + "references": [ + { + "field": "optional.AccessControlAdminFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Admin/AccessControlAdminMod.sol", + "path": "src/access/AccessControl/Admin/AccessControlAdminMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlAdminFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Admin/AccessControlAdminFacet.sol", + "path": "src/access/AccessControl/Admin/AccessControlAdminFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlGrantBatchFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Batch/Grant/AccessControlGrantBatchMod.sol", + "path": "src/access/AccessControl/Batch/Grant/AccessControlGrantBatchMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlGrantBatchFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Batch/Grant/AccessControlGrantBatchFacet.sol", + "path": "src/access/AccessControl/Batch/Grant/AccessControlGrantBatchFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlPausableFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Pausable/AccessControlPausableMod.sol", + "path": "src/access/AccessControl/Pausable/AccessControlPausableMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlPausableFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Pausable/AccessControlPausableFacet.sol", + "path": "src/access/AccessControl/Pausable/AccessControlPausableFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlRenounceFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Renounce/AccessControlRenounceMod.sol", + "path": "src/access/AccessControl/Renounce/AccessControlRenounceMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlRenounceFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Renounce/AccessControlRenounceFacet.sol", + "path": "src/access/AccessControl/Renounce/AccessControlRenounceFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlRevokeBatchFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchMod.sol", + "path": "src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlRevokeBatchFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.sol", + "path": "src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlTemporalDataFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Temporal/Data/AccessControlTemporalDataMod.sol", + "path": "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlTemporalDataFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.sol", + "path": "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlTemporalGrantFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantMod.sol", + "path": "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.AccessControlTemporalGrantFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.sol", + "path": "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "required.AccessControlDataFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Data/AccessControlDataMod.sol", + "path": "src/access/AccessControl/Data/AccessControlDataMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.AccessControlDataFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Data/AccessControlDataFacet.sol", + "path": "src/access/AccessControl/Data/AccessControlDataFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.AccessControlGrantFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Grant/AccessControlGrantMod.sol", + "path": "src/access/AccessControl/Grant/AccessControlGrantMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.AccessControlGrantFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Grant/AccessControlGrantFacet.sol", + "path": "src/access/AccessControl/Grant/AccessControlGrantFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.AccessControlRevokeFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Revoke/AccessControlRevokeMod.sol", + "path": "src/access/AccessControl/Revoke/AccessControlRevokeMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.AccessControlRevokeFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/AccessControl/Revoke/AccessControlRevokeFacet.sol", + "path": "src/access/AccessControl/Revoke/AccessControlRevokeFacet.sol", + "role": "required", + "status": "resolved" + } + ] + }, + { + "id": "counter", + "sourcePath": "cli/bases/counter.json", + "definition": { + "label": "Counter", + "order": 2, + "required": { + "CounterDataFacet": { + "path": "./src/templates/counter/CounterDataFacet.sol" + }, + "CounterIncrementFacet": { + "path": "./src/templates/counter/CounterIncrementFacet.sol" + } + }, + "optional": { + "CounterDecrementFacet": { + "path": "./src/templates/counter/CounterDecrementFacet.sol" + }, + "CounterResetFacet": { + "path": "./src/templates/counter/CounterResetFacet.sol" + }, + "CounterSetFacet": { + "path": "./src/templates/counter/CounterSetFacet.sol" + } + } + }, + "references": [ + { + "field": "optional.CounterDecrementFacet.path", + "declaredPath": "./src/templates/counter/CounterDecrementFacet.sol", + "path": null, + "role": "optional", + "status": "external" + }, + { + "field": "optional.CounterResetFacet.path", + "declaredPath": "./src/templates/counter/CounterResetFacet.sol", + "path": null, + "role": "optional", + "status": "external" + }, + { + "field": "optional.CounterSetFacet.path", + "declaredPath": "./src/templates/counter/CounterSetFacet.sol", + "path": null, + "role": "optional", + "status": "external" + }, + { + "field": "required.CounterDataFacet.path", + "declaredPath": "./src/templates/counter/CounterDataFacet.sol", + "path": null, + "role": "required", + "status": "external" + }, + { + "field": "required.CounterIncrementFacet.path", + "declaredPath": "./src/templates/counter/CounterIncrementFacet.sol", + "path": null, + "role": "required", + "status": "external" + } + ] + }, + { + "id": "diamond", + "sourcePath": "cli/bases/diamond.json", + "definition": { + "label": "Diamond", + "pragma": ">=0.8.30", + "compilerVersion": "0.8.30", + "required": { + "DiamondInspectFacet": { + "path": "@perfect-abstractions/compose/diamond/DiamondInspectFacet.sol" + } + }, + "optional": { + "DiamondUpgradeFacet": { + "path": "@perfect-abstractions/compose/diamond/DiamondUpgradeFacet.sol" + } + } + }, + "references": [ + { + "field": "optional.DiamondUpgradeFacet.path", + "declaredPath": "@perfect-abstractions/compose/diamond/DiamondUpgradeFacet.sol", + "path": "src/diamond/DiamondUpgradeFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "required.DiamondInspectFacet.path", + "declaredPath": "@perfect-abstractions/compose/diamond/DiamondInspectFacet.sol", + "path": "src/diamond/DiamondInspectFacet.sol", + "role": "required", + "status": "resolved" + } + ] + }, + { + "id": "erc-1155", + "sourcePath": "cli/bases/erc1155.json", + "definition": { + "label": "ERC-1155", + "order": 5, + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC1155.sol", + "id": "type(IERC1155).interfaceId", + "comments": [ + "Register ERC-1155 interface support." + ] + }, + "required": { + "ERC1155DataFacet": { + "path": "@perfect-abstractions/compose/token/ERC1155/Data/ERC1155DataFacet.sol" + }, + "ERC1155ApproveFacet": { + "path": "@perfect-abstractions/compose/token/ERC1155/Approve/ERC1155ApproveFacet.sol", + "mod": "@perfect-abstractions/compose/token/ERC1155/Approve/ERC1155ApproveMod.sol" + }, + "ERC1155TransferFacet": { + "path": "@perfect-abstractions/compose/token/ERC1155/Transfer/ERC1155TransferFacet.sol", + "mod": "@perfect-abstractions/compose/token/ERC1155/Transfer/ERC1155TransferMod.sol" + } + }, + "optional": { + "ERC1155BurnFacet": { + "path": "@perfect-abstractions/compose/token/ERC1155/Burn/ERC1155BurnFacet.sol", + "mod": "@perfect-abstractions/compose/token/ERC1155/Burn/ERC1155BurnMod.sol" + }, + "ERC1155MetadataFacet": { + "path": "@perfect-abstractions/compose/token/ERC1155/Metadata/ERC1155MetadataFacet.sol", + "mod": "@perfect-abstractions/compose/token/ERC1155/Metadata/ERC1155MetadataMod.sol", + "constructor": [ + { + "comments": [ + "TODO: Review the sample ERC-1155 metadata URI before deployment." + ], + "code": "ERC1155MetadataMod.setURI(\"https://example.com/metadata/{id}.json\");" + } + ], + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC1155Metadata_URI.sol", + "id": "type(IERC1155Metadata_URI).interfaceId", + "comments": [ + "Register ERC-1155 metadata URI interface support." + ] + } + }, + "RoyaltyFacet": { + "path": "@perfect-abstractions/compose/token/Royalty/RoyaltyFacet.sol", + "mod": "@perfect-abstractions/compose/token/Royalty/RoyaltyMod.sol", + "constructor": [ + { + "comments": [ + "TODO: Replace the sample royalty receiver and fee before deployment." + ], + "code": "RoyaltyMod.setDefaultRoyalty(0x0000000000000000000000000000000000000001, 500);" + } + ], + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC2981.sol", + "id": "type(IERC2981).interfaceId", + "comments": [ + "Register ERC-2981 royalty interface support." + ] + } + } + } + }, + "references": [ + { + "field": "erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC1155.sol", + "path": "src/interfaces/IERC1155.sol", + "role": null, + "status": "resolved" + }, + { + "field": "optional.ERC1155BurnFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/ERC1155/Burn/ERC1155BurnMod.sol", + "path": "src/token/ERC1155/Burn/ERC1155BurnMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC1155BurnFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC1155/Burn/ERC1155BurnFacet.sol", + "path": "src/token/ERC1155/Burn/ERC1155BurnFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC1155MetadataFacet.erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC1155Metadata_URI.sol", + "path": "src/interfaces/IERC1155Metadata_URI.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC1155MetadataFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/ERC1155/Metadata/ERC1155MetadataMod.sol", + "path": "src/token/ERC1155/Metadata/ERC1155MetadataMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC1155MetadataFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC1155/Metadata/ERC1155MetadataFacet.sol", + "path": "src/token/ERC1155/Metadata/ERC1155MetadataFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC2981.sol", + "path": "src/interfaces/IERC2981.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/Royalty/RoyaltyMod.sol", + "path": "src/token/Royalty/RoyaltyMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/Royalty/RoyaltyFacet.sol", + "path": "src/token/Royalty/RoyaltyFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "required.ERC1155ApproveFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/ERC1155/Approve/ERC1155ApproveMod.sol", + "path": "src/token/ERC1155/Approve/ERC1155ApproveMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC1155ApproveFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC1155/Approve/ERC1155ApproveFacet.sol", + "path": "src/token/ERC1155/Approve/ERC1155ApproveFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC1155DataFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC1155/Data/ERC1155DataFacet.sol", + "path": "src/token/ERC1155/Data/ERC1155DataFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC1155TransferFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/ERC1155/Transfer/ERC1155TransferMod.sol", + "path": "src/token/ERC1155/Transfer/ERC1155TransferMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC1155TransferFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC1155/Transfer/ERC1155TransferFacet.sol", + "path": "src/token/ERC1155/Transfer/ERC1155TransferFacet.sol", + "role": "required", + "status": "resolved" + } + ] + }, + { + "id": "erc-20", + "sourcePath": "cli/bases/erc20.json", + "definition": { + "label": "ERC-20", + "order": 2, + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC20.sol", + "id": "type(IERC20).interfaceId", + "comments": [ + "Register ERC-20 interface support." + ] + }, + "required": { + "ERC20DataFacet": { + "path": "@perfect-abstractions/compose/token/ERC20/Data/ERC20DataFacet.sol" + }, + "ERC20ApproveFacet": { + "path": "@perfect-abstractions/compose/token/ERC20/Approve/ERC20ApproveFacet.sol" + }, + "ERC20TransferFacet": { + "path": "@perfect-abstractions/compose/token/ERC20/Transfer/ERC20TransferFacet.sol" + } + }, + "optional": { + "ERC20BurnFacet": { + "path": "@perfect-abstractions/compose/token/ERC20/Burn/ERC20BurnFacet.sol" + }, + "ERC20MetadataFacet": { + "path": "@perfect-abstractions/compose/token/ERC20/Metadata/ERC20MetadataFacet.sol", + "mod": "@perfect-abstractions/compose/token/ERC20/Metadata/ERC20MetadataMod.sol", + "constructor": [ + { + "comments": [ + "TODO: Review the sample token name, symbol, and decimals before deployment." + ], + "code": "ERC20MetadataMod.setMetadata({_name: \"ExampleToken\", _symbol: \"EXT\", _decimals: 18});" + } + ] + }, + "ERC20PermitFacet": { + "path": "@perfect-abstractions/compose/token/ERC20/Permit/ERC20PermitFacet.sol" + }, + "ERC20BridgeableFacet": { + "path": "@perfect-abstractions/compose/token/ERC20/Bridgeable/ERC20BridgeableFacet.sol" + } + } + }, + "references": [ + { + "field": "erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC20.sol", + "path": "src/interfaces/IERC20.sol", + "role": null, + "status": "resolved" + }, + { + "field": "optional.ERC20BridgeableFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC20/Bridgeable/ERC20BridgeableFacet.sol", + "path": "src/token/ERC20/Bridgeable/ERC20BridgeableFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC20BurnFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC20/Burn/ERC20BurnFacet.sol", + "path": "src/token/ERC20/Burn/ERC20BurnFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC20MetadataFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/ERC20/Metadata/ERC20MetadataMod.sol", + "path": "src/token/ERC20/Metadata/ERC20MetadataMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC20MetadataFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC20/Metadata/ERC20MetadataFacet.sol", + "path": "src/token/ERC20/Metadata/ERC20MetadataFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC20PermitFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC20/Permit/ERC20PermitFacet.sol", + "path": "src/token/ERC20/Permit/ERC20PermitFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "required.ERC20ApproveFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC20/Approve/ERC20ApproveFacet.sol", + "path": "src/token/ERC20/Approve/ERC20ApproveFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC20DataFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC20/Data/ERC20DataFacet.sol", + "path": "src/token/ERC20/Data/ERC20DataFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC20TransferFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC20/Transfer/ERC20TransferFacet.sol", + "path": "src/token/ERC20/Transfer/ERC20TransferFacet.sol", + "role": "required", + "status": "resolved" + } + ] + }, + { + "id": "erc-6909", + "sourcePath": "cli/bases/erc6909.json", + "definition": { + "label": "ERC-6909", + "order": 6, + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC6909.sol", + "id": "type(IERC6909).interfaceId", + "comments": [ + "Register ERC-6909 interface support." + ] + }, + "required": { + "ERC6909DataFacet": { + "path": "@perfect-abstractions/compose/token/ERC6909/Data/ERC6909DataFacet.sol" + }, + "ERC6909TransferFacet": { + "path": "@perfect-abstractions/compose/token/ERC6909/Transfer/ERC6909TransferFacet.sol", + "mod": "@perfect-abstractions/compose/token/ERC6909/Transfer/ERC6909TransferMod.sol" + }, + "ERC6909ApproveFacet": { + "path": "@perfect-abstractions/compose/token/ERC6909/Approve/ERC6909ApproveFacet.sol", + "mod": "@perfect-abstractions/compose/token/ERC6909/Approve/ERC6909ApproveMod.sol" + }, + "ERC6909OperatorFacet": { + "path": "@perfect-abstractions/compose/token/ERC6909/Operator/ERC6909OperatorFacet.sol", + "mod": "@perfect-abstractions/compose/token/ERC6909/Operator/ERC6909OperatorMod.sol" + } + }, + "optional": { + "ERC6909BurnFacet": { + "path": "@perfect-abstractions/compose/token/ERC6909/Burn/ERC6909BurnFacet.sol", + "mod": "@perfect-abstractions/compose/token/ERC6909/Burn/ERC6909BurnMod.sol" + }, + "RoyaltyFacet": { + "path": "@perfect-abstractions/compose/token/Royalty/RoyaltyFacet.sol", + "mod": "@perfect-abstractions/compose/token/Royalty/RoyaltyMod.sol", + "constructor": [ + { + "comments": [ + "TODO: Replace the sample royalty receiver and fee before deployment." + ], + "code": "RoyaltyMod.setDefaultRoyalty(0x0000000000000000000000000000000000000001, 500);" + } + ], + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC2981.sol", + "id": "type(IERC2981).interfaceId", + "comments": [ + "Register ERC-2981 royalty interface support." + ] + } + } + } + }, + "references": [ + { + "field": "erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC6909.sol", + "path": "src/interfaces/IERC6909.sol", + "role": null, + "status": "resolved" + }, + { + "field": "optional.ERC6909BurnFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/ERC6909/Burn/ERC6909BurnMod.sol", + "path": "src/token/ERC6909/Burn/ERC6909BurnMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC6909BurnFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC6909/Burn/ERC6909BurnFacet.sol", + "path": "src/token/ERC6909/Burn/ERC6909BurnFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC2981.sol", + "path": "src/interfaces/IERC2981.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/Royalty/RoyaltyMod.sol", + "path": "src/token/Royalty/RoyaltyMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/Royalty/RoyaltyFacet.sol", + "path": "src/token/Royalty/RoyaltyFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "required.ERC6909ApproveFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/ERC6909/Approve/ERC6909ApproveMod.sol", + "path": "src/token/ERC6909/Approve/ERC6909ApproveMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC6909ApproveFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC6909/Approve/ERC6909ApproveFacet.sol", + "path": "src/token/ERC6909/Approve/ERC6909ApproveFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC6909DataFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC6909/Data/ERC6909DataFacet.sol", + "path": "src/token/ERC6909/Data/ERC6909DataFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC6909OperatorFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/ERC6909/Operator/ERC6909OperatorMod.sol", + "path": "src/token/ERC6909/Operator/ERC6909OperatorMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC6909OperatorFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC6909/Operator/ERC6909OperatorFacet.sol", + "path": "src/token/ERC6909/Operator/ERC6909OperatorFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC6909TransferFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/ERC6909/Transfer/ERC6909TransferMod.sol", + "path": "src/token/ERC6909/Transfer/ERC6909TransferMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC6909TransferFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC6909/Transfer/ERC6909TransferFacet.sol", + "path": "src/token/ERC6909/Transfer/ERC6909TransferFacet.sol", + "role": "required", + "status": "resolved" + } + ] + }, + { + "id": "erc-721", + "sourcePath": "cli/bases/erc721.json", + "definition": { + "label": "ERC-721", + "order": 3, + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC721.sol", + "id": "type(IERC721).interfaceId", + "comments": [ + "Register ERC-721 interface support." + ] + }, + "required": { + "ERC721ApproveFacet": { + "path": "@perfect-abstractions/compose/token/ERC721/Approve/ERC721ApproveFacet.sol" + }, + "ERC721DataFacet": { + "path": "@perfect-abstractions/compose/token/ERC721/Data/ERC721DataFacet.sol" + }, + "ERC721TransferFacet": { + "path": "@perfect-abstractions/compose/token/ERC721/Transfer/ERC721TransferFacet.sol" + } + }, + "optional": { + "ERC721BurnFacet": { + "path": "@perfect-abstractions/compose/token/ERC721/Burn/ERC721BurnFacet.sol" + }, + "ERC721MetadataFacet": { + "path": "@perfect-abstractions/compose/token/ERC721/Metadata/ERC721MetadataFacet.sol", + "mod": "@perfect-abstractions/compose/token/ERC721/Metadata/ERC721MetadataMod.sol", + "constructor": [ + { + "comments": [ + "TODO: Review the sample collection name, symbol, and base URI before deployment." + ], + "code": "ERC721MetadataMod.setMetadata({_name: \"ExampleDiamondNFT\", _symbol: \"EDN\", _baseURI: \"https://example.com/metadata/\"});" + } + ], + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC721Metadata.sol", + "id": "type(IERC721Metadata).interfaceId", + "comments": [ + "Register ERC-721 Metadata interface support." + ] + } + }, + "RoyaltyFacet": { + "path": "@perfect-abstractions/compose/token/Royalty/RoyaltyFacet.sol", + "mod": "@perfect-abstractions/compose/token/Royalty/RoyaltyMod.sol", + "constructor": [ + { + "comments": [ + "TODO: Replace the sample royalty receiver and fee before deployment." + ], + "code": "RoyaltyMod.setDefaultRoyalty(0x0000000000000000000000000000000000000001, 500);" + } + ], + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC2981.sol", + "id": "type(IERC2981).interfaceId", + "comments": [ + "Register ERC-2981 royalty interface support." + ] + } + } + } + }, + "references": [ + { + "field": "erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC721.sol", + "path": "src/interfaces/IERC721.sol", + "role": null, + "status": "resolved" + }, + { + "field": "optional.ERC721BurnFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Burn/ERC721BurnFacet.sol", + "path": "src/token/ERC721/Burn/ERC721BurnFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC721MetadataFacet.erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC721Metadata.sol", + "path": "src/interfaces/IERC721Metadata.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC721MetadataFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Metadata/ERC721MetadataMod.sol", + "path": "src/token/ERC721/Metadata/ERC721MetadataMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC721MetadataFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Metadata/ERC721MetadataFacet.sol", + "path": "src/token/ERC721/Metadata/ERC721MetadataFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC2981.sol", + "path": "src/interfaces/IERC2981.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/Royalty/RoyaltyMod.sol", + "path": "src/token/Royalty/RoyaltyMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/Royalty/RoyaltyFacet.sol", + "path": "src/token/Royalty/RoyaltyFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "required.ERC721ApproveFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Approve/ERC721ApproveFacet.sol", + "path": "src/token/ERC721/Approve/ERC721ApproveFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC721DataFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Data/ERC721DataFacet.sol", + "path": "src/token/ERC721/Data/ERC721DataFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC721TransferFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Transfer/ERC721TransferFacet.sol", + "path": "src/token/ERC721/Transfer/ERC721TransferFacet.sol", + "role": "required", + "status": "resolved" + } + ] + }, + { + "id": "erc-721-enumerable", + "sourcePath": "cli/bases/erc721.json", + "definition": { + "label": "ERC-721 Enumerable", + "order": 4, + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC721.sol", + "id": "type(IERC721).interfaceId", + "comments": [ + "Register ERC-721 interface support." + ] + }, + "required": { + "ERC721ApproveFacet": { + "path": "@perfect-abstractions/compose/token/ERC721/Approve/ERC721ApproveFacet.sol" + }, + "ERC721DataFacet": { + "path": "@perfect-abstractions/compose/token/ERC721/Data/ERC721DataFacet.sol" + }, + "ERC721EnumerableDataFacet": { + "path": "@perfect-abstractions/compose/token/ERC721/Enumerable/Data/ERC721EnumerableDataFacet.sol" + }, + "ERC721EnumerableTransferFacet": { + "path": "@perfect-abstractions/compose/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.sol" + } + }, + "optional": { + "ERC721EnumerableBurnFacet": { + "path": "@perfect-abstractions/compose/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnFacet.sol" + }, + "ERC721MetadataFacet": { + "path": "@perfect-abstractions/compose/token/ERC721/Metadata/ERC721MetadataFacet.sol", + "mod": "@perfect-abstractions/compose/token/ERC721/Metadata/ERC721MetadataMod.sol", + "constructor": [ + { + "comments": [ + "TODO: Review the sample collection name, symbol, and base URI before deployment." + ], + "code": "ERC721MetadataMod.setMetadata({_name: \"ExampleDiamondNFT\", _symbol: \"EDN\", _baseURI: \"https://example.com/metadata/\"});" + } + ], + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC721Metadata.sol", + "id": "type(IERC721Metadata).interfaceId", + "comments": [ + "Register ERC-721 Metadata interface support." + ] + } + }, + "RoyaltyFacet": { + "path": "@perfect-abstractions/compose/token/Royalty/RoyaltyFacet.sol", + "mod": "@perfect-abstractions/compose/token/Royalty/RoyaltyMod.sol", + "constructor": [ + { + "comments": [ + "TODO: Replace the sample royalty receiver and fee before deployment." + ], + "code": "RoyaltyMod.setDefaultRoyalty(0x0000000000000000000000000000000000000001, 500);" + } + ], + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC2981.sol", + "id": "type(IERC2981).interfaceId", + "comments": [ + "Register ERC-2981 royalty interface support." + ] + } + } + } + }, + "references": [ + { + "field": "erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC721.sol", + "path": "src/interfaces/IERC721.sol", + "role": null, + "status": "resolved" + }, + { + "field": "optional.ERC721EnumerableBurnFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnFacet.sol", + "path": "src/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC721MetadataFacet.erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC721Metadata.sol", + "path": "src/interfaces/IERC721Metadata.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC721MetadataFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Metadata/ERC721MetadataMod.sol", + "path": "src/token/ERC721/Metadata/ERC721MetadataMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.ERC721MetadataFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Metadata/ERC721MetadataFacet.sol", + "path": "src/token/ERC721/Metadata/ERC721MetadataFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC2981.sol", + "path": "src/interfaces/IERC2981.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.mod", + "declaredPath": "@perfect-abstractions/compose/token/Royalty/RoyaltyMod.sol", + "path": "src/token/Royalty/RoyaltyMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.RoyaltyFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/Royalty/RoyaltyFacet.sol", + "path": "src/token/Royalty/RoyaltyFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "required.ERC721ApproveFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Approve/ERC721ApproveFacet.sol", + "path": "src/token/ERC721/Approve/ERC721ApproveFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC721DataFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Data/ERC721DataFacet.sol", + "path": "src/token/ERC721/Data/ERC721DataFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC721EnumerableDataFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Enumerable/Data/ERC721EnumerableDataFacet.sol", + "path": "src/token/ERC721/Enumerable/Data/ERC721EnumerableDataFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.ERC721EnumerableTransferFacet.path", + "declaredPath": "@perfect-abstractions/compose/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.sol", + "path": "src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.sol", + "role": "required", + "status": "resolved" + } + ] + }, + { + "id": "libraries", + "sourcePath": "cli/bases/libraries.json", + "definition": { + "label": "Libraries", + "required": {}, + "optional": { + "ERC165Facet": { + "path": "@perfect-abstractions/compose/interfaceDetection/ERC165/ERC165Facet.sol" + } + } + }, + "references": [ + { + "field": "optional.ERC165Facet.path", + "declaredPath": "@perfect-abstractions/compose/interfaceDetection/ERC165/ERC165Facet.sol", + "path": "src/interfaceDetection/ERC165/ERC165Facet.sol", + "role": "optional", + "status": "resolved" + } + ] + }, + { + "id": "owner", + "sourcePath": "cli/bases/owner.json", + "definition": { + "label": "Owner", + "order": 7, + "access": true, + "accessType": "ownership", + "erc165": { + "path": "@perfect-abstractions/compose/interfaces/IERC173.sol", + "id": "type(IERC173).interfaceId", + "comments": [ + "Register ERC-173 ownership interface support." + ] + }, + "required": { + "OwnerDataFacet": { + "path": "@perfect-abstractions/compose/access/Owner/Data/OwnerDataFacet.sol", + "mod": "@perfect-abstractions/compose/access/Owner/Data/OwnerDataMod.sol", + "constructor": [ + { + "comments": [ + "TODO: Review the initial owner policy before deployment." + ], + "code": "OwnerDataMod.setContractOwner(msg.sender);" + } + ] + }, + "OwnerTransferFacet": { + "path": "@perfect-abstractions/compose/access/Owner/Transfer/OwnerTransferFacet.sol", + "mod": "@perfect-abstractions/compose/access/Owner/Transfer/OwnerTransferMod.sol" + } + }, + "optional": { + "OwnerRenounceFacet": { + "path": "@perfect-abstractions/compose/access/Owner/Renounce/OwnerRenounceFacet.sol", + "mod": "@perfect-abstractions/compose/access/Owner/Renounce/OwnerRenounceMod.sol" + } + } + }, + "references": [ + { + "field": "erc165.path", + "declaredPath": "@perfect-abstractions/compose/interfaces/IERC173.sol", + "path": "src/interfaces/IERC173.sol", + "role": null, + "status": "resolved" + }, + { + "field": "optional.OwnerRenounceFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/Owner/Renounce/OwnerRenounceMod.sol", + "path": "src/access/Owner/Renounce/OwnerRenounceMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.OwnerRenounceFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/Owner/Renounce/OwnerRenounceFacet.sol", + "path": "src/access/Owner/Renounce/OwnerRenounceFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "required.OwnerDataFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/Owner/Data/OwnerDataMod.sol", + "path": "src/access/Owner/Data/OwnerDataMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.OwnerDataFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/Owner/Data/OwnerDataFacet.sol", + "path": "src/access/Owner/Data/OwnerDataFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.OwnerTransferFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/Owner/Transfer/OwnerTransferMod.sol", + "path": "src/access/Owner/Transfer/OwnerTransferMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.OwnerTransferFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/Owner/Transfer/OwnerTransferFacet.sol", + "path": "src/access/Owner/Transfer/OwnerTransferFacet.sol", + "role": "required", + "status": "resolved" + } + ] + }, + { + "id": "owner-two-step", + "sourcePath": "cli/bases/owner.json", + "definition": { + "label": "Owner Two-Step", + "order": 8, + "access": true, + "accessType": "ownership", + "required": { + "OwnerDataFacet": { + "path": "@perfect-abstractions/compose/access/Owner/Data/OwnerDataFacet.sol", + "mod": "@perfect-abstractions/compose/access/Owner/Data/OwnerDataMod.sol", + "constructor": [ + { + "comments": [ + "TODO: Review the initial owner policy before deployment." + ], + "code": "OwnerDataMod.setContractOwner(msg.sender);" + } + ] + }, + "OwnerTwoStepDataFacet": { + "path": "@perfect-abstractions/compose/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.sol", + "mod": "@perfect-abstractions/compose/access/Owner/TwoSteps/Data/OwnerTwoStepDataMod.sol" + }, + "OwnerTwoStepTransferFacet": { + "path": "@perfect-abstractions/compose/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferFacet.sol", + "mod": "@perfect-abstractions/compose/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferMod.sol" + } + }, + "optional": { + "OwnerTwoStepRenounceFacet": { + "path": "@perfect-abstractions/compose/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceFacet.sol", + "mod": "@perfect-abstractions/compose/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceMod.sol" + } + } + }, + "references": [ + { + "field": "optional.OwnerTwoStepRenounceFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceMod.sol", + "path": "src/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceMod.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "optional.OwnerTwoStepRenounceFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceFacet.sol", + "path": "src/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceFacet.sol", + "role": "optional", + "status": "resolved" + }, + { + "field": "required.OwnerDataFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/Owner/Data/OwnerDataMod.sol", + "path": "src/access/Owner/Data/OwnerDataMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.OwnerDataFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/Owner/Data/OwnerDataFacet.sol", + "path": "src/access/Owner/Data/OwnerDataFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.OwnerTwoStepDataFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/Owner/TwoSteps/Data/OwnerTwoStepDataMod.sol", + "path": "src/access/Owner/TwoSteps/Data/OwnerTwoStepDataMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.OwnerTwoStepDataFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.sol", + "path": "src/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.OwnerTwoStepTransferFacet.mod", + "declaredPath": "@perfect-abstractions/compose/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferMod.sol", + "path": "src/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferMod.sol", + "role": "required", + "status": "resolved" + }, + { + "field": "required.OwnerTwoStepTransferFacet.path", + "declaredPath": "@perfect-abstractions/compose/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferFacet.sol", + "path": "src/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferFacet.sol", + "role": "required", + "status": "resolved" + } + ] + } + ] +}