diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index f1e7f838..ae3547c7 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -22,6 +22,9 @@ jobs: - name: Check documented dependency versions run: bun scripts/check-versions.ts + - name: Check documentation structure and links + run: bun scripts/check-docs.ts + - name: Build Vocs run: cd vocs && bun install --frozen-lockfile && bun run build @@ -29,6 +32,11 @@ jobs: run: | test -f vocs/dist/public/sitemap.xml test -f vocs/dist/public/robots.txt + test -f vocs/dist/public/llms.txt + test -f vocs/dist/public/llms-full.txt + test -f vocs/dist/public/agent-index.json + if grep -q '](/' vocs/dist/public/llms.txt; then exit 1; fi + if grep -q '](/' vocs/dist/public/llms-full.txt; then exit 1; fi test ! -d vocs/dist/public/templates # Only prepare for deploy if a push to main diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..5553e62d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,43 @@ +# Alloy documentation agent guide + +## Scope and source of truth + +- Hand-written pages live in `vocs/docs/pages`; primary navigation lives in `vocs/sidebar.ts`. +- `vocs/versions.ts` is the single version source for hand-written dependency snippets. +- Generated example pages and snippets come from `alloy-rs/examples`. Do not edit files carrying a + `DO NOT EDIT` header; update the source example or template and run `scripts/update.sh`. +- Verify exact Alloy symbols and feature gates against the matching release on docs.rs or the + `alloy-rs/alloy` source. The website is explanatory documentation, not the API definition. + +## Content rules + +- Use task-oriented titles and frontmatter descriptions containing the terms a reader will search. +- State required Cargo features, environment variables, node capabilities, and external binaries. +- Use `RPC_URL`, `WS_URL`, and `IPC_PATH` instead of shared endpoints or credentials. +- Link concepts to a runnable example and exact API documentation where possible. +- Distinguish primitives, consensus types, RPC types, and network-associated types. +- Keep internal links root-relative; use absolute URLs for other repositories and sites. + +## Validation + +Run from the repository root: + +```sh +bun scripts/check-versions.ts +bun scripts/check-docs.ts +actionlint +shellcheck scripts/update.sh +cd vocs && bun install --frozen-lockfile && bun run build +``` + +The build post-processes `llms.txt`, `llms-full.txt`, and `agent-index.json`. Confirm all three exist +under `vocs/dist/public`. Compile new Rust snippets against the documented Alloy release; rendering +Markdown is not proof that a snippet compiles. + +## Agent retrieval artifacts + +- `https://alloy.rs/agent-index.json`: versioned page inventory and task-to-page map. +- `https://alloy.rs/llms.txt`: compact page inventory. +- `https://alloy.rs/llms-full.txt`: complete corpus; use only when page retrieval is unavailable. +- `https://github.com/alloy-rs/examples/blob/main/examples-index.json`: runnable example metadata once + the corresponding examples catalog change is available on the default branch. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 36ff9951..0cf14feb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -122,6 +122,10 @@ To perform an update of generated output inspect and run `./scripts/update.sh`. To add a new page, create it under `./vocs/docs/pages` and add it to `./vocs/sidebar.ts` when it should appear in the primary navigation. +Every hand-written page must have a frontmatter description. Use root-relative internal links and +run `bun scripts/check-docs.ts` from the repository root before building. The build generates the +agent inventories, so new task areas should also be represented in `scripts/build-agent-index.ts`. + ### Discuss and update You will probably get feedback or requests for changes to your Pull Request. diff --git a/README.md b/README.md index 833016c7..3c553b4f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Documentation for all things Alloy. View the docs [here](https://alloy.rs/). +Machine-readable discovery is available through +[`agent-index.json`](https://alloy.rs/agent-index.json), [`llms.txt`](https://alloy.rs/llms.txt), and +[`llms-full.txt`](https://alloy.rs/llms-full.txt). + ## Contributing Thanks for your help improving the project! We are so happy to have you! We have diff --git a/scripts/build-agent-index.ts b/scripts/build-agent-index.ts new file mode 100644 index 00000000..50b1dcb1 --- /dev/null +++ b/scripts/build-agent-index.ts @@ -0,0 +1,321 @@ +import { existsSync } from 'node:fs' +import { readFile, writeFile } from 'node:fs/promises' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { versions } from '../vocs/versions' + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const publicDir = join(root, 'vocs/dist/public') +const pagesDir = join(root, 'vocs/docs/pages') +const examplesDir = join(root, 'lib/examples') +const baseUrl = 'https://alloy.rs' + +function proseSegments(markdown: string, transform: (segment: string) => string): string { + const fence = /^[ \t]*(?:```|~~~).*$/gm + let inFence = false + let cursor = 0 + let output = '' + + for (const match of markdown.matchAll(fence)) { + const index = match.index + const segment = markdown.slice(cursor, index) + output += inFence ? segment : transform(segment) + output += match[0] + cursor = index + match[0].length + inFence = !inFence + } + + const segment = markdown.slice(cursor) + return output + (inFence ? segment : transform(segment)) +} + +function absoluteLinks(markdown: string): string { + return proseSegments(markdown, (segment) => + segment + .replace(/(!?\[[^\]]*]\()\/(?!\/)/g, `$1${baseUrl}/`) + .replace(/(\b(?:href|src)\s*=\s*["'])\/(?!\/)/g, `$1${baseUrl}/`), + ) +} + +function unresolvedLinks(markdown: string): string[] { + const unresolved: string[] = [] + proseSegments(markdown, (segment) => { + const targets = [ + ...[...segment.matchAll(/!?\[[^\]]*]\((]*>?)/g)].map((match) => match[1]), + ...[...segment.matchAll(/\b(?:href|src)\s*=\s*["']([^"']*)["']/g)].map( + (match) => match[1], + ), + ] + + for (const rawTarget of targets) { + const target = rawTarget.replace(/^<|>$/g, '') + if (/^[a-z][a-z+.-]*:/i.test(target) || target.startsWith('//')) continue + unresolved.push(target || '(empty target)') + } + return segment + }) + return unresolved +} + +const llmsPath = join(publicDir, 'llms.txt') +const llmsFullPath = join(publicDir, 'llms-full.txt') +const llms = absoluteLinks(await readFile(llmsPath, 'utf8')) +const llmsFull = absoluteLinks(await readFile(llmsFullPath, 'utf8')) + +await writeFile(llmsPath, llms) +await writeFile(llmsFullPath, llmsFull) + +const unresolved = [...unresolvedLinks(llms), ...unresolvedLinks(llmsFull)] +if (unresolved.length) { + throw new Error(`Agent text still contains unresolved links: ${[...new Set(unresolved)].join(', ')}`) +} + +const sourceByRoute = new Map() +const exampleSourceByRoute = new Map() +for (const file of new Bun.Glob('**/*.{md,mdx}').scanSync({ cwd: pagesDir, onlyFiles: true })) { + const pathname = `/${file.replace(/\\/g, '/').replace(/\.(md|mdx)$/, '')}` + sourceByRoute.set(pathname, file) + + if (!pathname.startsWith('/examples/') || pathname.endsWith('/README')) continue + + const text = await readFile(join(pagesDir, file), 'utf8') + const match = /https:\/\/github\.com\/alloy-rs\/examples\/(?:blob|tree)\/[^/)\s]+\/(examples\/[^)\s]+\.rs)/.exec(text) + if (!match) throw new Error(`Generated example page ${pathname} has no source metadata`) + if (!existsSync(join(examplesDir, match[1]))) { + throw new Error(`Generated example page ${pathname} references missing source ${match[1]}`) + } + + exampleSourceByRoute.set(pathname, match[0].replace('/tree/', '/blob/')) +} + +function exampleSourceFor(pathname: string): string | null { + if (!pathname.startsWith('/examples/') || pathname.endsWith('/README')) return null + + const source = exampleSourceByRoute.get(pathname) + if (!source) throw new Error(`Example page ${pathname} has no verified source URL`) + return source +} + +function kindFor(pathname: string): string { + if (pathname.startsWith('/examples/')) return 'example' + if (pathname.startsWith('/migrating-')) return 'migration' + if (pathname.startsWith('/reference/')) return 'reference' + if (pathname.startsWith('/introduction/')) return 'introduction' + return 'guide' +} + +const pages = [...llms.matchAll(/^- \[([^\]]+)]\((https:\/\/alloy\.rs\/[^)]+)\)(?:: (.*))?$/gm)] + .map((match) => { + const url = match[2] + const pathname = new URL(url).pathname + const source = sourceByRoute.get(pathname) + const parts = pathname.split('/').filter(Boolean) + + return { + id: pathname, + title: match[1], + description: match[3] ?? '', + kind: kindFor(pathname), + section: parts[0] ?? 'home', + url, + source_url: source + ? `https://github.com/alloy-rs/docs/blob/main/vocs/docs/pages/${source}` + : null, + example_source_url: exampleSourceFor(pathname), + } + }) + .filter((page, index, all) => all.findIndex((candidate) => candidate.url === page.url) === index) + +const indexedPaths = new Set(pages.map((page) => page.id)) +for (const [pathname, source] of sourceByRoute) { + if (indexedPaths.has(pathname)) continue + + const text = await readFile(join(pagesDir, source), 'utf8') + const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text)?.[1] ?? '' + const frontmatterTitle = /^title:\s*(.+)$/m.exec(frontmatter)?.[1] + const heading = /^#\s+(.+)$/m.exec(text)?.[1] + const parts = pathname.split('/').filter(Boolean) + const category = parts[1] + const name = parts[2] + const fallbackName = (name === 'README' ? `${category} examples` : parts.at(-1) ?? 'Alloy') + .replace(/[-_]/g, ' ') + .replace(/\b\w/g, (character) => character.toUpperCase()) + + pages.push({ + id: pathname, + title: frontmatterTitle ?? heading ?? fallbackName, + description: + /^description:\s*(.+)$/m.exec(frontmatter)?.[1] ?? + (name === 'README' ? `Runnable Alloy ${category} examples` : ''), + kind: kindFor(pathname), + section: parts[0] ?? 'home', + url: `${baseUrl}${pathname}`, + source_url: `https://github.com/alloy-rs/docs/blob/main/vocs/docs/pages/${source}`, + example_source_url: exampleSourceFor(pathname), + }) + indexedPaths.add(pathname) +} + +const url = (path: string) => `${baseUrl}${path}` +const topics = [ + { + id: 'install-and-features', + intent: 'Install Alloy or choose a minimal dependency and feature set', + keywords: ['cargo', 'install', 'dependency', 'feature', 'no_std', 'wasm'], + docs: [url('/introduction/installation'), url('/reference/feature-flags')], + examples: [], + features: [], + configuration: [], + }, + { + id: 'connect-provider', + intent: 'Connect to an RPC endpoint over HTTP, WebSocket, or IPC', + keywords: ['provider', 'rpc', 'http', 'websocket', 'ws', 'ipc', 'ProviderBuilder'], + docs: [url('/rpc-providers/introduction'), url('/introduction/getting-started')], + examples: [url('/examples/providers/http'), url('/examples/providers/ws'), url('/examples/providers/ipc')], + features: ['provider-http', 'provider-ws', 'provider-ipc'], + configuration: ['RPC_URL', 'WS_URL', 'IPC_PATH'], + }, + { + id: 'stream-chain-data', + intent: 'Subscribe, poll, resume block or log processing, and handle reorgs', + keywords: ['subscribe', 'watch', 'poll', 'block', 'log', 'indexer', 'reorg', 'backfill'], + docs: [url('/rpc-providers/subscriptions-and-polling')], + examples: [url('/examples/subscriptions/subscribe_blocks'), url('/examples/subscriptions/subscribe_logs')], + features: ['provider-ws'], + configuration: ['WS_URL'], + }, + { + id: 'contract-interaction', + intent: 'Generate typed contract bindings and read, write, deploy, or query events', + keywords: ['contract', 'sol!', 'abi', 'call', 'deploy', 'event', 'revert'], + docs: [url('/contract-interactions/using-sol!'), url('/contract-interactions/read-contract'), url('/contract-interactions/write-contract')], + examples: [url('/examples/contracts/README')], + features: ['contract'], + configuration: ['RPC_URL'], + }, + { + id: 'transactions', + intent: 'Build, fill, sign, send, and confirm a transaction', + keywords: ['transaction', 'fill', 'sign', 'send', 'receipt', 'confirm', 'nonce', 'gas'], + docs: [url('/transactions/introduction'), url('/transactions/transaction-lifecycle')], + examples: [url('/examples/transactions/README')], + features: ['provider-http', 'signer-local'], + configuration: ['RPC_URL', 'PRIVATE_KEY'], + }, + { + id: 'blob-transactions', + intent: 'Build EIP-4844 blobs or EIP-7594 PeerDAS sidecars', + keywords: ['blob', 'EIP-4844', 'EIP-7594', 'PeerDAS', 'KZG', 'sidecar'], + docs: [url('/transactions/sending-an-EIP-4844-transaction'), url('/transactions/sending-an-EIP-7594-transaction')], + examples: [url('/examples/transactions/send_eip4844_transaction'), url('/examples/transactions/send_eip7594_transaction')], + features: ['kzg'], + configuration: [], + }, + { + id: 'signers-and-wallets', + intent: 'Choose a local, hardware, cloud, or remote signer and configure EthereumWallet', + keywords: ['signer', 'wallet', 'private key', 'mnemonic', 'keystore', 'AWS', 'GCP', 'Ledger', 'Trezor', 'Turnkey'], + docs: [url('/guides/signers-vs-ethereum-wallet')], + examples: [url('/examples/wallets/README'), url('/examples/wallets/ethereum_wallet')], + features: ['signer-local'], + configuration: ['PRIVATE_KEY'], + }, + { + id: 'testing', + intent: 'Test with a mock transport, local node, pinned fork, or live RPC', + keywords: ['test', 'mock', 'Anvil', 'Geth', 'Reth', 'fork', 'fixture'], + docs: [url('/guides/testing')], + examples: [url('/examples/providers/mocking'), url('/examples/node-bindings/README')], + features: ['provider-anvil-node'], + configuration: ['RPC_URL'], + }, + { + id: 'multiple-networks', + intent: 'Use Ethereum, AnyNetwork, an ecosystem network, or a custom Network', + keywords: ['Network', 'AnyNetwork', 'Optimism', 'Base', 'custom chain', 'deserialization'], + docs: [url('/guides/interacting-with-multiple-networks'), url('/reference/protocol-and-rpc-types')], + examples: [url('/examples/advanced/any_network')], + features: ['network'], + configuration: ['RPC_URL'], + }, + { + id: 'provider-reliability', + intent: 'Configure retries, rate limits, fallbacks, timeouts, and error handling', + keywords: ['retry', 'backoff', 'rate limit', 'fallback', 'timeout', 'TransportError'], + docs: [url('/rpc-providers/reliability'), url('/guides/layers')], + examples: [url('/examples/layers/retry_layer'), url('/examples/layers/fallback_layer')], + features: ['transport-throttle'], + configuration: ['RPC_URL', 'RPC_URLS'], + }, + { + id: 'rpc-namespaces', + intent: 'Use debug, trace, txpool, engine, admin, Anvil, MEV, or other RPC methods', + keywords: ['debug', 'trace', 'txpool', 'engine', 'admin', 'Anvil', 'MEV', 'extension trait'], + docs: [url('/rpc-providers/rpc-namespaces')], + examples: [url('/examples/transactions/trace_call'), url('/examples/transactions/debug_trace_call_many')], + features: ['provider-debug-api', 'provider-trace-api'], + configuration: ['RPC_URL'], + }, + { + id: 'types-and-serialization', + intent: 'Choose primitives, consensus, RPC, EIP, network, genesis, trie, or serde types', + keywords: ['primitive', 'consensus', 'rpc type', 'EIP', 'genesis', 'trie', 'serde', 'RLP'], + docs: [url('/reference/protocol-and-rpc-types'), url('/using-primitive-types/introduction')], + examples: [url('/examples/providers/embed_consensus_rpc')], + features: ['consensus', 'eips', 'serde'], + configuration: [], + }, + { + id: 'abi-encoding', + intent: 'Encode or decode static Solidity types, dynamic ABI, JSON ABI, logs, errors, or call data', + keywords: ['ABI', 'encode', 'decode', 'sol!', 'DynSol', 'JsonAbi', 'event', 'error'], + docs: [url('/guides/static-dynamic-abi-in-alloy'), url('/contract-interactions/using-sol!')], + examples: [url('/examples/sol-macro/README'), url('/examples/advanced/encoding_dyn_abi')], + features: ['sol-types', 'dyn-abi', 'json-abi'], + configuration: [], + }, + { + id: 'migrate-from-ethers', + intent: 'Map ethers-rs crates, types, middleware, and conversions to Alloy', + keywords: ['ethers-rs', 'migration', 'Middleware', 'conversion'], + docs: [url('/migrating-from-ethers/reference'), url('/migrating-from-ethers/conversions')], + examples: [], + features: [], + configuration: [], + }, +] + +const pageUrls = new Set(pages.map((page) => page.url)) +for (const topic of topics) { + for (const target of [...topic.docs, ...topic.examples]) { + if (!pageUrls.has(target)) throw new Error(`Agent topic ${topic.id} references missing page ${target}`) + } +} + +const index = { + schema_version: 1, + project: 'Alloy', + canonical_base_url: baseUrl, + target_versions: { alloy: versions.alloy, alloy_core: versions.alloyCore }, + retrieval_order: [ + `${baseUrl}/agent-index.json`, + `${baseUrl}/llms.txt`, + 'the smallest relevant page and linked runnable examples', + 'https://docs.rs/alloy/latest/alloy/ for exact symbols and feature gates', + `${baseUrl}/llms-full.txt only when individual page retrieval is unavailable`, + ], + resources: { + agent_guide: `${baseUrl}/introduction/prompting`, + compact_corpus: `${baseUrl}/llms.txt`, + full_corpus: `${baseUrl}/llms-full.txt`, + api_reference: 'https://docs.rs/alloy/latest/alloy/', + examples_repository: 'https://github.com/alloy-rs/examples', + source_repository: 'https://github.com/alloy-rs/alloy', + }, + topics, + pages, +} + +await writeFile(join(publicDir, 'agent-index.json'), `${JSON.stringify(index, null, 2)}\n`) +console.log(`Generated agent-index.json with ${topics.length} topics and ${pages.length} pages.`) diff --git a/scripts/check-docs.ts b/scripts/check-docs.ts new file mode 100644 index 00000000..69b536b0 --- /dev/null +++ b/scripts/check-docs.ts @@ -0,0 +1,139 @@ +import { existsSync, readFileSync } from 'node:fs' +import { dirname, join, posix, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const pagesRoot = join(root, 'vocs/docs/pages') +const publicRoot = join(root, 'vocs/public') +const sidebarPath = join(root, 'vocs/sidebar.ts') + +const pageFiles = [ + ...new Bun.Glob('**/*.{md,mdx}').scanSync({ cwd: pagesRoot, onlyFiles: true }), +].sort() + +function routeFor(file: string): string { + return `/${file.replace(/\\/g, '/').replace(/\.(md|mdx)$/, '')}` +} + +function headingAnchors(markdown: string): Set { + const anchors = new Set() + const seen = new Map() + let inFence = false + + for (const line of markdown.split(/\r?\n/)) { + if (/^\s*```/.test(line)) { + inFence = !inFence + continue + } + if (inFence) continue + + const match = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line) + if (!match) continue + + const base = match[2] + .replace(/<[^>]+>/g, '') + .replace(/\[([^\]]+)]\([^)]+\)/g, '$1') + .replace(/[`*_~]/g, '') + .toLowerCase() + .replace(/[^\p{Letter}\p{Number}\s_-]/gu, '') + .trim() + .replace(/\s+/g, '-') + + const count = seen.get(base) ?? 0 + seen.set(base, count + 1) + anchors.add(count === 0 ? base : `${base}-${count}`) + } + + return anchors +} + +const pages = new Map }>() +for (const file of pageFiles) { + const text = readFileSync(join(pagesRoot, file), 'utf8') + pages.set(routeFor(file), { file, text, anchors: headingAnchors(text) }) +} + +const errors: string[] = [] + +function validateTarget(sourceRoute: string, sourceFile: string, rawTarget: string): void { + if (!rawTarget || /^[a-z][a-z+.-]*:/i.test(rawTarget) || rawTarget.startsWith('//')) return + + const target = rawTarget.replace(/^<|>$/g, '') + if (target.startsWith('~')) return + + const [withoutHash, rawAnchor] = target.split('#', 2) + const withoutQuery = withoutHash.split('?', 1)[0] + let route = sourceRoute + + if (target.startsWith('#')) { + errors.push(`${sourceFile}: internal links must be root-relative: ${rawTarget}`) + } + + if (withoutQuery) { + if (!withoutQuery.startsWith('/')) { + errors.push(`${sourceFile}: internal links must be root-relative: ${rawTarget}`) + } + + if (withoutQuery.startsWith('/')) { + route = withoutQuery.length > 1 ? withoutQuery.replace(/\/$/, '') : '/' + if (route === '/' || existsSync(join(publicRoot, route.slice(1)))) return + } else { + route = posix.normalize(posix.join(posix.dirname(sourceRoute), withoutQuery)) + route = route.replace(/\.(md|mdx)$/, '').replace(/\/$/, '') + if (!route.startsWith('/')) route = `/${route}` + } + } + + const targetPage = pages.get(route) + if (!targetPage) { + errors.push(`${sourceFile}: internal target does not exist: ${rawTarget}`) + return + } + + if (rawAnchor) { + const anchor = decodeURIComponent(rawAnchor).toLowerCase() + if (!targetPage.anchors.has(anchor)) { + errors.push(`${sourceFile}: heading does not exist: ${rawTarget}`) + } + } +} + +for (const [route, page] of pages) { + const isGeneratedExample = page.file.startsWith('examples/') + if (!isGeneratedExample) { + const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(page.text)?.[1] + if (!frontmatter || !/^description:\s*\S+/m.test(frontmatter)) { + errors.push(`${page.file}: hand-written pages require a frontmatter description`) + } + + for (const forbidden of [ + 'ethereum.reth.rs', + 'reth-ethereum.ithaca.xyz', + 'base-sepolia.ithaca.xyz', + 't.me/ethers_rs', + ]) { + if (page.text.toLowerCase().includes(forbidden)) { + errors.push(`${page.file}: contains retired or shared endpoint/reference: ${forbidden}`) + } + } + } + + for (const match of page.text.matchAll(/!?\[[^\]]*]\((]+>?)/g)) { + validateTarget(route, page.file, match[1]) + } + for (const match of page.text.matchAll(/\b(?:href|src)\s*=\s*["']([^"']+)["']/g)) { + validateTarget(route, page.file, match[1]) + } +} + +const sidebar = readFileSync(sidebarPath, 'utf8') +for (const match of sidebar.matchAll(/\blink:\s*['"]([^'"]+)['"]/g)) { + validateTarget('/index', 'vocs/sidebar.ts', match[1]) +} + +if (errors.length) { + for (const error of [...new Set(errors)].sort()) console.error(error) + process.exit(1) +} + +console.log(`Validated ${pages.size} documentation routes and their internal links.`) diff --git a/vocs/docs/pages/guides/interacting-with-multiple-networks.md b/vocs/docs/pages/guides/interacting-with-multiple-networks.md index 22c03c54..b74dd71f 100644 --- a/vocs/docs/pages/guides/interacting-with-multiple-networks.md +++ b/vocs/docs/pages/guides/interacting-with-multiple-networks.md @@ -41,9 +41,11 @@ impl Network for Ethereum { Choosing the wrong network type can lead to unexpected deserialization errors due to differences in RPC types. For example, the using an `Ethereum` network provider to get a full block with transactions can result in the following error: ```rust [base_block.rs] +let rpc_url = std::env::var("RPC_URL")?; let provider = ProviderBuilder::new() .network::() - .connect_http("https://base-sepolia.ithaca.xyz/".parse()?); + .connect(&rpc_url) + .await?; // Yields: Error: deserialization error: data did not match any variant of untagged enum BlockTransactions // [!code hl] let block_with_txs = provider.get_block(25508329.into()).full().await?; diff --git a/vocs/docs/pages/guides/static-dynamic-abi-in-alloy.mdx b/vocs/docs/pages/guides/static-dynamic-abi-in-alloy.mdx index 039b5032..d19dbb44 100644 --- a/vocs/docs/pages/guides/static-dynamic-abi-in-alloy.mdx +++ b/vocs/docs/pages/guides/static-dynamic-abi-in-alloy.mdx @@ -29,7 +29,9 @@ sol!( ); ``` -You can find the complete example and ABI artifacts [here](). +You can find the complete [static encoding example](https://github.com/alloy-rs/examples/blob/main/examples/advanced/examples/encoding_sol_static.rs), +[dynamic encoding example](https://github.com/alloy-rs/examples/blob/main/examples/advanced/examples/encoding_dyn_abi.rs), +and [ABI artifacts](https://github.com/alloy-rs/examples/tree/main/examples/advanced/examples/abi) in the examples repository. Let's look into these examples in more detail. diff --git a/vocs/docs/pages/introduction/getting-started.md b/vocs/docs/pages/introduction/getting-started.md index 62621843..70f95320 100644 --- a/vocs/docs/pages/introduction/getting-started.md +++ b/vocs/docs/pages/introduction/getting-started.md @@ -16,7 +16,7 @@ Alloy is a high-performance Rust toolkit for Ethereum and EVM-based blockchains ## Installation -Install alloy to any cargo project using the command line. See [Installation](/introduction/installation#features) for more details on the various features flags. +Install Alloy in any Cargo project using the command line. See [feature flags](/reference/feature-flags) for task-specific and minimal configurations. ```bash [cargo] cargo add alloy @@ -281,7 +281,7 @@ Meta-crates can lead to dependencies bloat increasing compile-time. For large pr This allows you to have granular control over the dependencies and features you use in your project. -Find the full list of the crates [here](/introduction/installation#crates). +See [how Alloy fits together](/introduction/architecture) for the crate and runtime layers. ### `no_std` crates diff --git a/vocs/docs/pages/introduction/prompting.md b/vocs/docs/pages/introduction/prompting.md index dd130d23..55bda876 100644 --- a/vocs/docs/pages/introduction/prompting.md +++ b/vocs/docs/pages/introduction/prompting.md @@ -9,14 +9,18 @@ Alloy publishes machine-readable documentation for coding agents. Give an agent ## Recommended retrieval order -1. Start with [`llms.txt`](https://alloy.rs/llms.txt) to discover the canonical page for the task. -2. Load the relevant guide and any pages it links to. -3. Find the closest runnable example in the [Alloy examples repository](https://github.com/alloy-rs/examples). +1. Query [`agent-index.json`](https://alloy.rs/agent-index.json) when the client can read JSON. It + maps task vocabulary, features, environment variables, guides, and examples. +2. Otherwise start with [`llms.txt`](https://alloy.rs/llms.txt) to discover the canonical page. +3. Load the smallest relevant guide and the runnable examples it links to. 4. Use [docs.rs](https://docs.rs/alloy/latest/alloy/) for exact types, traits, methods, and feature gates. 5. Use [`llms-full.txt`](https://alloy.rs/llms-full.txt) only when the agent cannot retrieve pages individually. The Alloy version displayed in this site's navigation is the version targeted by hand-written documentation. Generated example pages identify the exact examples commit they were copied from. +All URLs in the published agent inventories are absolute. Cache them with the documented Alloy +version and invalidate the cache when that version changes. + ## Starter prompt Use this as a short foundation, then append the task, target chain, transport, signer, and runtime constraints. diff --git a/vocs/docs/pages/introduction/why-alloy.mdx b/vocs/docs/pages/introduction/why-alloy.mdx index 43c7f413..97e39567 100644 --- a/vocs/docs/pages/introduction/why-alloy.mdx +++ b/vocs/docs/pages/introduction/why-alloy.mdx @@ -1,7 +1,12 @@ +--- +description: Understand Alloy's modular architecture, performance goals, provider model, and differences from ethers-rs +--- + # Why Alloy? Alloy is the next-generation Rust toolkit for Ethereum development and a complete rewrite of [ethers-rs](https://www.gakonst.com/ethers-rs/getting-started/intro.html). Alloy offers a modular, high-performance, and developer-friendly experience for building on EVM-compatible chains. -It addresses the various pain points of existing tooling in the Rust Ethereum ecosystem, such as perfomance bottlenecks, cumbersome APIs and having to deal with feature flag that bloat your application. +It addresses pain points in existing Rust Ethereum tooling, such as performance bottlenecks, +cumbersome APIs, and feature flags that bloat applications. --- diff --git a/vocs/docs/pages/migrating-from-ethers/reference.md b/vocs/docs/pages/migrating-from-ethers/reference.md index 8700e9a9..f09537b9 100644 --- a/vocs/docs/pages/migrating-from-ethers/reference.md +++ b/vocs/docs/pages/migrating-from-ethers/reference.md @@ -23,7 +23,7 @@ The following is a reference guide for finding the migration path for your speci - Compilers: [`ethers::solc`](https://github.com/gakonst/ethers-rs/tree/master/ethers-solc) `->` [`foundry-compilers`](https://github.com/foundry-rs/compilers) - Contract: [`ethers::contract`](https://github.com/gakonst/ethers-rs/tree/master/ethers-contract) `->` [`alloy::contract`](https://github.com/alloy-rs/alloy/tree/main/crates/contract) - Core: [`ethers::core`](https://github.com/gakonst/ethers-rs/tree/master/ethers-core) `->` [`alloy::core`](https://github.com/alloy-rs/core) - - Types: [`ethers::core::types::*`](https://github.com/gakonst/ethers-rs/tree/master/ethers-core/src/types) `->` See [Types](#types) section + - Types: [`ethers::core::types::*`](https://github.com/gakonst/ethers-rs/tree/master/ethers-core/src/types) `->` See [Types](/migrating-from-ethers/reference#types) section - Etherscan: [`ethers::etherscan`](https://github.com/gakonst/ethers-rs/tree/master/ethers-etherscan) `->` [`foundry-block-explorers`](https://github.com/foundry-rs/block-explorers) - Middleware: [`ethers::middleware`](https://github.com/gakonst/ethers-rs/tree/master/ethers-middleware) `->` Fillers [`alloy::provider::{fillers, layers}`](https://github.com/alloy-rs/alloy/tree/main/crates/provider/src) - Gas oracle: [`ethers::middleware::GasOracleMiddleware`](https://github.com/gakonst/ethers-rs/tree/master/ethers-middleware/src/gas_oracle/middleware.rs) `->` Gas filler [`alloy::provider::GasFiller`](https://github.com/alloy-rs/examples/tree/main/examples/fillers/examples/gas_filler.rs) diff --git a/vocs/docs/pages/migrating-to-core-v1/encoding-decoding-changes/README.md b/vocs/docs/pages/migrating-to-core-v1/encoding-decoding-changes/README.md index 492ff9bd..11961fd0 100644 --- a/vocs/docs/pages/migrating-to-core-v1/encoding-decoding-changes/README.md +++ b/vocs/docs/pages/migrating-to-core-v1/encoding-decoding-changes/README.md @@ -4,5 +4,5 @@ description: Overview of ABI encoding and decoding changes in Alloy v1.0 ## Simply ABI encoding and decoding -- [ABI encoding function return structs](./encoding-return-structs.md) -- [Removing `validate: bool` from the `abi_decode` methods](./removing-validate-bool.md) +- [ABI encoding function return structs](/migrating-to-core-v1/encoding-decoding-changes/encoding-return-structs) +- [Removing `validate: bool` from the `abi_decode` methods](/migrating-to-core-v1/encoding-decoding-changes/removing-validate-bool) diff --git a/vocs/docs/pages/migrating-to-core-v1/sol!-changes/README.md b/vocs/docs/pages/migrating-to-core-v1/sol!-changes/README.md index dd9f2f2d..ca422db0 100644 --- a/vocs/docs/pages/migrating-to-core-v1/sol!-changes/README.md +++ b/vocs/docs/pages/migrating-to-core-v1/sol!-changes/README.md @@ -4,8 +4,8 @@ description: Overview of changes to the sol! macro bindings in Alloy v1.0 ## sol! changes -- [Removing the `T` transport generic](./removing-T-generic.md) -- [Improving function return type](./improving-function-return-types.md) -- [Changes to function call bindings](./changes-to-function-call-bindings.md) -- [Changes to event bindings](./changes-to-event-bindings.md) -- [Changes to error bindings](./changes-to-error-bindings.md) +- [Removing the `T` transport generic](/migrating-to-core-v1/sol!-changes/removing-T-generic) +- [Improving function return type](/migrating-to-core-v1/sol!-changes/improving-function-return-types) +- [Changes to function call bindings](/migrating-to-core-v1/sol!-changes/changes-to-function-call-bindings) +- [Changes to event bindings](/migrating-to-core-v1/sol!-changes/changes-to-event-bindings) +- [Changes to error bindings](/migrating-to-core-v1/sol!-changes/changes-to-error-bindings) diff --git a/vocs/docs/pages/rpc-providers/introduction.md b/vocs/docs/pages/rpc-providers/introduction.md index e85184d2..30561005 100644 --- a/vocs/docs/pages/rpc-providers/introduction.md +++ b/vocs/docs/pages/rpc-providers/introduction.md @@ -10,7 +10,7 @@ A [`Provider`](https://docs.rs/alloy/latest/alloy/providers/trait.Provider.html) The correct way of creating a [`Provider`](https://docs.rs/alloy/latest/alloy/providers/trait.Provider.html) is through the [`ProviderBuilder`](https://docs.rs/alloy/latest/alloy/providers/struct.ProviderBuilder.html), a [builder](https://rust-unofficial.github.io/patterns/patterns/creational/builder.html). -Alloy provides concrete transport implementations for [`HTTP`](/rpc-providers/http-provider), [`WS` (WebSockets)](/rpc-providers/ws-provider) and [`IPC` (Inter-Process Communication)](/rpc-providers/ipc-provider.md), as well as higher level transports which wrap a single or multiple transports. +Alloy provides concrete transport implementations for [`HTTP`](/rpc-providers/http-provider), [`WS` (WebSockets)](/rpc-providers/ws-provider) and [`IPC` (Inter-Process Communication)](/rpc-providers/ipc-provider), as well as higher level transports which wrap a single or multiple transports. The [`connect`](https://docs.rs/alloy/latest/alloy/providers/struct.ProviderBuilder.html#method.connect) method on the [`ProviderBuilder`](https://docs.rs/alloy/latest/alloy/providers/struct.ProviderBuilder.html) will automatically determine the connection type (`Http`, `Ws` or `Ipc`) depending on the format of the URL. diff --git a/vocs/docs/pages/using-primitive-types/big-numbers.md b/vocs/docs/pages/using-primitive-types/big-numbers.md index 55b46c4d..9e3046be 100644 --- a/vocs/docs/pages/using-primitive-types/big-numbers.md +++ b/vocs/docs/pages/using-primitive-types/big-numbers.md @@ -1,3 +1,7 @@ +--- +description: Initialize Alloy U256 and other fixed-width integer values from common Rust representations +--- + ## Initializing Big Numbers ```rust diff --git a/vocs/docs/pages/using-primitive-types/common-conversions.md b/vocs/docs/pages/using-primitive-types/common-conversions.md index 48c53e32..78b1a31d 100644 --- a/vocs/docs/pages/using-primitive-types/common-conversions.md +++ b/vocs/docs/pages/using-primitive-types/common-conversions.md @@ -1,3 +1,7 @@ +--- +description: Convert between Alloy fixed-width integers, byte arrays, strings, and primitive Rust values +--- + ## Common conversions ```rust diff --git a/vocs/docs/pages/using-primitive-types/comparisons-and-equivalence.md b/vocs/docs/pages/using-primitive-types/comparisons-and-equivalence.md index e8277a80..c319cad4 100644 --- a/vocs/docs/pages/using-primitive-types/comparisons-and-equivalence.md +++ b/vocs/docs/pages/using-primitive-types/comparisons-and-equivalence.md @@ -1,3 +1,7 @@ +--- +description: Compare Alloy fixed-width integers and reason about equivalent values and representations +--- + ## Comparisons and equivalence ```rust diff --git a/vocs/docs/pages/using-primitive-types/hash-and-address-types.md b/vocs/docs/pages/using-primitive-types/hash-and-address-types.md index 134c2849..37dab01d 100644 --- a/vocs/docs/pages/using-primitive-types/hash-and-address-types.md +++ b/vocs/docs/pages/using-primitive-types/hash-and-address-types.md @@ -1,3 +1,7 @@ +--- +description: Create, parse, hash, and inspect Alloy byte strings and Ethereum addresses +--- + ## Basic hash and address types ### Bytes and Address diff --git a/vocs/docs/pages/using-primitive-types/using-big-numbers.md b/vocs/docs/pages/using-primitive-types/using-big-numbers.md index dbaa7790..591085f8 100644 --- a/vocs/docs/pages/using-primitive-types/using-big-numbers.md +++ b/vocs/docs/pages/using-primitive-types/using-big-numbers.md @@ -1,3 +1,7 @@ +--- +description: Perform arithmetic and convert human-readable units with Alloy fixed-width integers +--- + ## Using big numbers Ethereum uses big numbers (also known as "bignums" or "arbitrary-precision integers") to represent certain values in its codebase and in blockchain transactions. This is necessary because the [EVM](https://ethereum.org/en/developers/docs/evm) operates on a 256-bit word size, which is different from the usual 32-bit or 64-bit of modern machines. This was chosen for the ease of use with 256-bit cryptography (such as [Keccak-256](https://github.com/ethereum/eth-hash) hashes or [secp256k1](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm) signatures). diff --git a/vocs/package.json b/vocs/package.json index f8870072..e99e847b 100644 --- a/vocs/package.json +++ b/vocs/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vocs dev", - "build": "vocs build", + "build": "vocs build && bun ../scripts/build-agent-index.ts", "preview": "vocs preview" }, "dependencies": {