diff --git a/content/ecosystem-adapters/architecture.mdx b/content/ecosystem-adapters/architecture.mdx new file mode 100644 index 00000000..a2ab6efd --- /dev/null +++ b/content/ecosystem-adapters/architecture.mdx @@ -0,0 +1,192 @@ +--- +title: Architecture +--- + +This page describes the capability-based architecture that underpins all OpenZeppelin Ecosystem Adapters. Understanding this architecture will help you choose the right profile for your application, consume capabilities efficiently, and — if needed — build your own adapter. + +## Package Topology + +The adapter system is split across several packages with clear dependency boundaries: + +```mermaid +flowchart TD + App["Consumer Application"] --> Runtime["EcosystemRuntime"] + Runtime --> Caps["Capability Interfaces\n(@openzeppelin/ui-types)"] + + Caps --> Evm["adapter-evm"] + Caps --> Polkadot["adapter-polkadot"] + Caps --> Stellar["adapter-stellar"] + Caps --> Midnight["adapter-midnight"] + Caps --> Solana["adapter-solana"] + + App --> Vite["adapters-vite"] + + Evm --> Core["adapter-evm-core"] + Polkadot --> Core + Core --> Utils["adapter-runtime-utils"] + Stellar --> Utils + + style Caps fill:#e8eaf6,stroke:#3f51b5,color:#000 + style Utils fill:#e0f2f1,stroke:#00897b,color:#000 + style Core fill:#fff3e0,stroke:#ef6c00,color:#000 +``` + +- **`@openzeppelin/ui-types`** defines all 13 capability interfaces. It is the single source of truth. +- **`adapter-runtime-utils`** provides profile composition, lazy capability instantiation, and staged disposal. +- **`adapter-evm-core`** centralizes reusable EVM implementations shared by `adapter-evm` and `adapter-polkadot`. +- Each public adapter exposes an `ecosystemDefinition` conforming to `EcosystemExport`. + +## Capability Tiers + +Adapter functionality is decomposed into **13 capability interfaces** organized across **3 tiers**. The tiers reflect increasing levels of runtime requirements: stateless metadata, network-aware schema operations, and stateful wallet-dependent interactions. + +| Tier | Category | Network | Wallet | Capabilities | +| --- | --- | --- | --- | --- | +| **1** | Lightweight | No | No | `Addressing`, `Explorer`, `NetworkCatalog`, `UiLabels` | +| **2** | Schema | Yes | No | `ContractLoading`, `Schema`, `TypeMapping`, `Query` | +| **3** | Runtime | Yes | Yes | `Execution`, `Wallet`, `UiKit`, `Relayer`, `AccessControl` | + +### Tier Import Rules + +Tier isolation is enforced physically through sub-path exports, not tree-shaking: + +- **Tier 1** modules must not import from Tier 2 or Tier 3 modules +- **Tier 2** modules may import from Tier 1 +- **Tier 3** modules may import from Tier 1 and Tier 2 + +This means importing `@openzeppelin/adapter-evm/addressing` will never pull in wallet SDKs, RPC clients, or access control code — regardless of your bundler configuration. + +### Capability Reference + +| Capability | Interface | Tier | Key Methods | +| --- | --- | --- | --- | +| Addressing | `AddressingCapability` | 1 | `isValidAddress` | +| Explorer | `ExplorerCapability` | 1 | `getExplorerUrl`, `getExplorerTxUrl` | +| NetworkCatalog | `NetworkCatalogCapability` | 1 | `getNetworks` | +| UiLabels | `UiLabelsCapability` | 1 | `getUiLabels` | +| ContractLoading | `ContractLoadingCapability` | 2 | `loadContract`, `getContractDefinitionInputs` | +| Schema | `SchemaCapability` | 2 | `isViewFunction`, `getWritableFunctions` | +| TypeMapping | `TypeMappingCapability` | 2 | `mapParameterTypeToFieldType`, `getTypeMappingInfo` | +| Query | `QueryCapability` | 2 | `queryViewFunction`, `formatFunctionResult`, `getCurrentBlock` | +| Execution | `ExecutionCapability` | 3 | `signAndBroadcast`, `formatTransactionData`, `validateExecutionConfig` | +| Wallet | `WalletCapability` | 3 | `connectWallet`, `disconnectWallet`, `getWalletConnectionStatus` | +| UiKit | `UiKitCapability` | 3 | `getAvailableUiKits`, `configureUiKit` | +| Relayer | `RelayerCapability` | 3 | `getRelayers`, `getNetworkServiceForms` | +| AccessControl | `AccessControlCapability` | 3 | `registerContract`, `grantRole`, and 17 more | + +## Profiles + +Profiles are pre-composed bundles of capabilities that match common application archetypes. They exist for convenience — you can always consume individual capabilities directly via the `CapabilityFactoryMap`. + +Each profile is a strict superset of Declarative — higher profiles add capabilities incrementally: + +### Profile–Capability Matrix + +| Capability | Declarative | Viewer | Transactor | Composer | Operator | +| --- | --- | --- | --- | --- | --- | +| `Addressing` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `Explorer` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `NetworkCatalog` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `UiLabels` | ✅ | ✅ | ✅ | ✅ | ✅ | +| `ContractLoading` | | ✅ | ✅ | ✅ | ✅ | +| `Schema` | | ✅ | ✅ | ✅ | ✅ | +| `TypeMapping` | | ✅ | ✅ | ✅ | ✅ | +| `Query` | | ✅ | | ✅ | ✅ | +| `Execution` | | | ✅ | ✅ | ✅ | +| `Wallet` | | | ✅ | ✅ | ✅ | +| `UiKit` | | | | ✅ | ✅ | +| `Relayer` | | | | ✅ | | +| `AccessControl` | | | | | ✅ | + +### Profile Selection Guide + +| If your application needs to… | Choose | +| --- | --- | +| Validate addresses, list networks, link to explorers | **Declarative** | +| Read contract state without sending transactions | **Viewer** | +| Send transactions without reading contract state first | **Transactor** | +| Build full contract interaction UIs with relayer support | **Composer** | +| Manage contract roles and permissions | **Operator** | + +## Runtime Lifecycle + +Runtimes are **immutable** and **network-scoped**. When a user switches networks, the consuming application must dispose the current runtime and create a new one. + +```mermaid +sequenceDiagram + participant App as Application + participant ES as EcosystemExport + participant RT as EcosystemRuntime + participant Cap as Capabilities + + App->>ES: createRuntime('composer', networkA) + ES->>RT: Compose capabilities with shared state + RT-->>App: runtime (immutable) + + App->>RT: runtime.query.queryViewFunction(...) + RT->>Cap: Lazy-init Query capability + Cap-->>RT: result + + Note over App,RT: User switches to networkB + + App->>RT: runtime.dispose() + RT->>Cap: Staged cleanup (listeners → subscriptions → capabilities → RPC) + App->>ES: createRuntime('composer', networkB) + ES->>RT: New runtime with fresh state + RT-->>App: newRuntime +``` + +### Dispose Contract + +- `dispose()` is **idempotent** — calling it multiple times is a no-op +- After `dispose()`, any method or property access throws `RuntimeDisposedError` +- Pending async operations (e.g., in-flight `signAndBroadcast`) are rejected with `RuntimeDisposedError` +- Cleanup follows a staged order: mark disposed → reject pending operations → clean up listeners and subscriptions → dispose capabilities → release wallet and RPC resources +- Runtime disposal does **not** disconnect the wallet — disconnect is always an explicit user action + +## Execution Strategies + +The Execution capability uses a **strategy pattern** to support multiple transaction submission methods. Each adapter can provide its own set of strategies. + +```mermaid +flowchart TD + Exec["ExecutionCapability\nsignAndBroadcast()"] --> Config{"executionConfig.method"} + Config -->|"EOA"| EOA["EoaExecutionStrategy\nDirect wallet signing"] + Config -->|"Relayer"| Relay["RelayerExecutionStrategy\nOpenZeppelin Relayer"] + + EOA --> Wallet["Wallet signs tx"] + Relay --> RelayerSvc["Relayer service submits tx"] + + Wallet --> Confirm["waitForTransactionConfirmation()"] + RelayerSvc --> Confirm + + style Exec fill:#e8eaf6,stroke:#3f51b5,color:#000 + style EOA fill:#e8f5e9,stroke:#388e3c,color:#000 + style Relay fill:#fff3e0,stroke:#f57c00,color:#000 +``` + +The EVM and Stellar adapters ship with both EOA and Relayer strategies. Adapter authors can implement custom strategies by conforming to the `AdapterExecutionStrategy` interface. + +## Sub-Path Exports + +Each adapter publishes every implemented capability and profile as a dedicated sub-path export: + +```ts +// Tier 1 — no wallet, no RPC, no heavy dependencies +import { createAddressing } from '@openzeppelin/adapter-stellar/addressing'; +import { createExplorer } from '@openzeppelin/adapter-stellar/explorer'; + +// Tier 2 — network-aware +import { createQuery } from '@openzeppelin/adapter-stellar/query'; + +// Tier 3 — wallet-dependent +import { createExecution } from '@openzeppelin/adapter-stellar/execution'; + +// Profile runtimes +import { createRuntime } from '@openzeppelin/adapter-stellar/profiles/composer'; + +// Metadata and networks +import { networks } from '@openzeppelin/adapter-stellar/networks'; +``` + +This structure ensures that a Declarative-profile consumer never bundles wallet SDKs, and that individual capabilities can be tested in isolation. diff --git a/content/ecosystem-adapters/building-an-adapter.mdx b/content/ecosystem-adapters/building-an-adapter.mdx new file mode 100644 index 00000000..f97e8d90 --- /dev/null +++ b/content/ecosystem-adapters/building-an-adapter.mdx @@ -0,0 +1,333 @@ +--- +title: Building an Adapter +--- + +This guide walks through implementing a new ecosystem adapter from scratch. By the end, you'll have a working adapter that integrates with any application using the OpenZeppelin adapter architecture. + +## How Much Do You Need to Implement? + +Adapters are **incrementally adoptable**. You don't need to implement all 13 capabilities to ship a useful adapter. Start small and add capabilities as your ecosystem's support matures. + +```mermaid +flowchart TD + Start["Start Here"] --> T1["Implement Tier 1\n(4 capabilities)"] + T1 -->|"Unlocks"| Dec["Declarative Profile\nAddress validation, explorer links,\nnetwork catalogs"] + + T1 --> T2["Add Tier 2\n(4 capabilities)"] + T2 -->|"Unlocks"| View["Viewer Profile\nContract reading, schema parsing,\ntype mapping, queries"] + + T2 --> T3a["Add Execution + Wallet"] + T3a -->|"Unlocks"| Trans["Transactor Profile\nTransaction signing\nand broadcasting"] + + T3a --> T3b["Add UiKit + Relayer"] + T3b -->|"Unlocks"| Comp["Composer Profile\nFull UI Builder integration\nwith relayer support"] + + T3a --> T3c["Add UiKit + AccessControl"] + T3c -->|"Unlocks"| Op["Operator Profile\nRole and permission\nmanagement"] + + style Start fill:#e8eaf6,stroke:#3f51b5,color:#000 + style T1 fill:#e3f2fd,stroke:#1976d2,color:#000 + style T2 fill:#fff3e0,stroke:#f57c00,color:#000 + style T3a fill:#fce4ec,stroke:#c62828,color:#000 + style T3b fill:#f3e5f5,stroke:#7b1fa2,color:#000 + style T3c fill:#f3e5f5,stroke:#7b1fa2,color:#000 + style Dec fill:#e8f5e9,stroke:#388e3c,color:#000 + style View fill:#e8f5e9,stroke:#388e3c,color:#000 + style Trans fill:#e8f5e9,stroke:#388e3c,color:#000 + style Comp fill:#e8f5e9,stroke:#388e3c,color:#000 + style Op fill:#e8f5e9,stroke:#388e3c,color:#000 +``` + +## Package Structure + +Follow the standard adapter package layout: + +``` +packages/adapter-/ +├── src/ +│ ├── capabilities/ # Capability factory functions +│ │ ├── addressing.ts +│ │ ├── explorer.ts +│ │ ├── network-catalog.ts +│ │ ├── ui-labels.ts +│ │ ├── contract-loading.ts # Tier 2+ +│ │ ├── schema.ts +│ │ ├── type-mapping.ts +│ │ ├── query.ts +│ │ ├── execution.ts # Tier 3+ +│ │ ├── wallet.ts +│ │ ├── ui-kit.ts +│ │ ├── relayer.ts +│ │ ├── access-control.ts +│ │ └── index.ts +│ ├── profiles/ # Profile runtime factories +│ │ ├── shared-state.ts +│ │ └── index.ts +│ ├── networks/ # Network configurations +│ │ └── index.ts +│ ├── index.ts # ecosystemDefinition export +│ ├── metadata.ts +│ └── vite-config.ts +├── package.json +├── tsconfig.json +├── tsdown.config.ts +└── vitest.config.ts +``` + +Not every adapter needs every file. A Tier 1-only adapter may only have `addressing.ts`, `explorer.ts`, `network-catalog.ts`, and `ui-labels.ts` in `capabilities/`. + +## Implementation Guide + +### Step 1: Implement Tier 1 Capabilities + +Start with the four stateless capabilities that every adapter must provide: + +```ts +// capabilities/addressing.ts +import type { AddressingCapability } from '@openzeppelin/ui-types'; + +export function createAddressing(): AddressingCapability { + return { + isValidAddress(address: string): boolean { + // Your chain's address validation logic + return /^0x[0-9a-fA-F]{40}$/.test(address); + }, + }; +} +``` + +```ts +// capabilities/explorer.ts +import type { ExplorerCapability, NetworkConfig } from '@openzeppelin/ui-types'; + +export function createExplorer(config?: NetworkConfig): ExplorerCapability { + const baseUrl = config?.explorerUrl ?? 'https://explorer.example.com'; + return { + getExplorerUrl: (address) => `${baseUrl}/address/${address}`, + getExplorerTxUrl: (txHash) => `${baseUrl}/tx/${txHash}`, + }; +} +``` + +```ts +// capabilities/network-catalog.ts +import type { NetworkCatalogCapability } from '@openzeppelin/ui-types'; +import { networks } from '../networks'; + +export function createNetworkCatalog(): NetworkCatalogCapability { + return { getNetworks: () => networks }; +} +``` + +```ts +// capabilities/ui-labels.ts +import type { UiLabelsCapability } from '@openzeppelin/ui-types'; + +export function createUiLabels(): UiLabelsCapability { + return { + getUiLabels: () => ({ transactionLabel: 'Transaction' }), + }; +} +``` + +### Step 2: Define the CapabilityFactoryMap + +Wire all capability factories into the capability map: + +```ts +// capabilities/index.ts +import type { CapabilityFactoryMap } from '@openzeppelin/ui-types'; +import { createAddressing } from './addressing'; +import { createExplorer } from './explorer'; +import { createNetworkCatalog } from './network-catalog'; +import { createUiLabels } from './ui-labels'; + +export const capabilities: CapabilityFactoryMap = { + addressing: createAddressing, + explorer: createExplorer, + networkCatalog: createNetworkCatalog, + uiLabels: createUiLabels, +}; +``` + +### Step 3: Wire Profile Runtimes + +Use `adapter-runtime-utils` to compose capabilities into profile runtimes: + +```ts +// profiles/index.ts +import type { NetworkConfig, ProfileName } from '@openzeppelin/ui-types'; +import { createRuntimeFromFactories } from '@openzeppelin/adapter-runtime-utils'; +import { capabilities } from '../capabilities'; + +export function createRuntime( + profile: ProfileName, + config: NetworkConfig, + factoryMap = capabilities, + options?: { uiKit?: string } +) { + return createRuntimeFromFactories(profile, config, factoryMap, options); +} +``` + +`createRuntimeFromFactories` handles: +- Validating that all required capabilities for the profile are present +- Throwing `UnsupportedProfileError` with a list of missing capabilities if validation fails +- Lazy capability instantiation with caching +- Staged disposal + +### Step 4: Export the Ecosystem Definition + +The `ecosystemDefinition` is the public entry point that consuming applications use: + +```ts +// index.ts +import type { EcosystemExport } from '@openzeppelin/ui-types'; +import { capabilities } from './capabilities'; +import { metadata } from './metadata'; +import { networks } from './networks'; +import { createRuntime } from './profiles'; + +export const ecosystemDefinition: EcosystemExport = { + ...metadata, + networks, + capabilities, + createRuntime: (profile, config, options) => + createRuntime(profile, config, capabilities, options), +}; +``` + +### Step 5: Configure Sub-Path Exports + +Add sub-path exports to `package.json` for physical tier isolation: + +```json +{ + "exports": { + ".": { "import": "./dist/index.mjs" }, + "./addressing": { "import": "./dist/capabilities/addressing.mjs" }, + "./explorer": { "import": "./dist/capabilities/explorer.mjs" }, + "./network-catalog": { "import": "./dist/capabilities/network-catalog.mjs" }, + "./ui-labels": { "import": "./dist/capabilities/ui-labels.mjs" }, + "./metadata": { "import": "./dist/metadata.mjs" }, + "./networks": { "import": "./dist/networks.mjs" }, + "./vite-config": { "import": "./dist/vite-config.mjs" } + } +} +``` + +Add more entries as you implement higher-tier capabilities. + +### Step 6: Add a Vite Config Export + +Every adapter must publish a `./vite-config` entry that returns build-time requirements: + +```ts +// vite-config.ts +import type { UserConfig } from 'vite'; + +export function getViteConfig(): UserConfig { + return { + resolve: { + dedupe: ['@your-chain/sdk'], + }, + optimizeDeps: { + include: ['@your-chain/sdk'], + }, + }; +} +``` + +## Adding Higher-Tier Capabilities + +Once Tier 1 is working, add capabilities incrementally. Each Tier 2+ factory function must: + +1. Accept a `NetworkConfig` parameter +2. Return a capability object that includes a `dispose()` method +3. Follow the tier import rules (Tier 2 may import Tier 1, Tier 3 may import Tier 1 and 2) + +Here's an example Query capability: + +```ts +// capabilities/query.ts +import type { QueryCapability, NetworkConfig } from '@openzeppelin/ui-types'; + +export function createQuery(config: NetworkConfig): QueryCapability { + let client: YourRpcClient | null = null; + + function getClient() { + if (!client) client = new YourRpcClient(config.rpcUrl); + return client; + } + + return { + networkConfig: config, + + async queryViewFunction(address, functionId, args) { + return getClient().call(address, functionId, args); + }, + + formatFunctionResult(result, schema, functionId) { + return { raw: result, formatted: String(result) }; + }, + + async getCurrentBlock() { + return getClient().getBlockNumber(); + }, + + dispose() { + client?.close(); + client = null; + }, + }; +} +``` + +## Shared EVM Core + +If your chain is EVM-compatible, you don't need to reimplement everything. Depend on `@openzeppelin/adapter-evm-core` and reuse its capability implementations: + +```ts +import { createExecution } from '@openzeppelin/adapter-evm-core/execution'; +import { createQuery } from '@openzeppelin/adapter-evm-core/query'; +``` + +This is exactly what `adapter-polkadot` does — it delegates ABI loading, queries, transaction execution, and wallet infrastructure to the shared EVM core while adding Polkadot-specific metadata and network configurations. + +## Lazy Capability Factories + +For capabilities that depend on each other (e.g., Query needs ContractLoading for schema resolution), use `createLazyRuntimeCapabilityFactories` from `adapter-runtime-utils`: + +```ts +import { + createLazyRuntimeCapabilityFactories, +} from '@openzeppelin/adapter-runtime-utils'; + +const lazyFactories = createLazyRuntimeCapabilityFactories(config, { + contractLoading: () => createContractLoading(config), + query: (deps) => createQuery(config, { + getContractLoading: () => deps.contractLoading, + }), +}); +``` + +This avoids circular dependency issues while maintaining shared state within a profile runtime. + +## Contribution Checklist + +Before submitting your adapter: + +1. All Tier 1 capabilities are implemented and exported via sub-path exports +2. `ecosystemDefinition` conforms to `EcosystemExport` with `capabilities` and `createRuntime` +3. `metadata`, `networks`, and `vite-config` exports are present +4. Profile runtimes use `adapter-runtime-utils` for composition +5. Unsupported profiles throw `UnsupportedProfileError` with missing capabilities listed +6. Tier isolation is verified: Tier 1 sub-path imports don't pull Tier 2/3 dependencies +7. Tests cover each capability factory and profile runtime creation +8. `package.json` includes sub-path exports for every implemented capability and profile +9. `tsdown.config.ts` includes entry points for all sub-path exports +10. Build-time requirements are validated with `pnpm validate:vite-configs` + + +The `pnpm lint:adapters` CI check validates tier isolation and export structure automatically. Run it locally before submitting your PR. + diff --git a/content/ecosystem-adapters/getting-started.mdx b/content/ecosystem-adapters/getting-started.mdx new file mode 100644 index 00000000..915d3b35 --- /dev/null +++ b/content/ecosystem-adapters/getting-started.mdx @@ -0,0 +1,392 @@ +--- +title: Getting Started +--- + +This guide walks you through installing an adapter, creating a runtime, and performing your first on-chain interaction. + +## Prerequisites + +- Node.js >= 20.19.0 +- A package manager: pnpm (recommended), npm, or yarn + +## Installation + +Install the adapter for your target ecosystem along with the shared types package: + +**EVM (Ethereum, Polygon, etc.)** +```bash +pnpm add @openzeppelin/adapter-evm @openzeppelin/ui-types +``` + +**Stellar (Soroban)** +```bash +pnpm add @openzeppelin/adapter-stellar @openzeppelin/ui-types +``` + +**Polkadot** +```bash +pnpm add @openzeppelin/adapter-polkadot @openzeppelin/ui-types +``` + +If you're consuming multiple ecosystems, install all of them — each adapter is independent and tree-shakeable. + +## Quick Start + +### 1. Choose a Profile + +Pick the [profile](/ecosystem-adapters/architecture#profiles) that matches your application's needs: + +| Profile | What it gives you | +| --- | --- | +| `declarative` | Address validation, explorer links, network lists | +| `viewer` | + Contract reading, schema parsing, type mapping | +| `transactor` | + Transaction signing and broadcasting | +| `composer` | + Full UI integration with wallet and relayer | +| `operator` | + Role and access control management | + +### 2. Create a Runtime + +Every adapter exports an `ecosystemDefinition` object. Use its `createRuntime` method to compose a profile runtime: + +```ts +import { ecosystemDefinition } from '@openzeppelin/adapter-evm'; + +const network = ecosystemDefinition.networks[0]; // e.g., Ethereum Mainnet + +const runtime = ecosystemDefinition.createRuntime('viewer', network); +``` + +The `createRuntime` call is **synchronous** — it assembles capability instances immediately. Expensive initialization (RPC connections, wallet discovery) happens lazily on first use. + +If you request a profile the adapter doesn't fully support, `createRuntime` throws an `UnsupportedProfileError` listing the missing capabilities. + +### 3. Use Capabilities + +Access capabilities directly from the runtime object: + +```ts +// Validate an address (Tier 1 — instant, no network call) +const isValid = runtime.addressing.isValidAddress('0x1234...'); + +// Read contract state (Tier 2 — triggers RPC on first call) +const result = await runtime.query.queryViewFunction( + '0xContractAddress', + 'balanceOf', + ['0xOwnerAddress'] +); +const formatted = runtime.query.formatFunctionResult(result, schema, 'balanceOf'); +``` + +### 4. Dispose When Done + +Runtimes hold network connections and event listeners. Dispose them when switching networks or unmounting: + +```ts +runtime.dispose(); +// Any further access throws RuntimeDisposedError +``` + +## Consuming Individual Capabilities + +You don't have to use profiles. Import individual capability factories via sub-path exports for maximum control: + +```ts +import { createAddressing } from '@openzeppelin/adapter-evm/addressing'; +import { createExplorer } from '@openzeppelin/adapter-evm/explorer'; + +const addressing = createAddressing(); +const explorer = createExplorer(networkConfig); + +console.log(addressing.isValidAddress('0xABC...')); // true or false +console.log(explorer.getExplorerUrl('0xABC...')); // https://etherscan.io/address/0xABC... +``` + + +Standalone capability instances created from factory functions are fully isolated. They do not share state with profile runtimes or with each other. + + +## Vite / Vitest Integration + +Applications that consume multiple adapter packages should use `@openzeppelin/adapters-vite` to merge adapter-owned build configuration: + +```bash +pnpm add -D @openzeppelin/adapters-vite +``` + +### Vite Config + +```ts +import { defineOpenZeppelinAdapterViteConfig } from '@openzeppelin/adapters-vite'; +import react from '@vitejs/plugin-react'; + +export default defineOpenZeppelinAdapterViteConfig({ + ecosystems: ['evm', 'stellar', 'polkadot'], + config: { + plugins: [react()], + }, +}); +``` + +### Vitest Config + +```ts +import { defineOpenZeppelinAdapterVitestConfig } from '@openzeppelin/adapters-vite'; +import { mergeConfig } from 'vitest/config'; + +export default mergeConfig( + sharedVitestConfig, + await defineOpenZeppelinAdapterVitestConfig({ + ecosystems: ['evm', 'stellar'], + importMetaUrl: import.meta.url, + config: { + test: { environment: 'jsdom' }, + }, + }) +); +``` + +This centralizes `resolve.dedupe`, `optimizeDeps`, `ssr.noExternal`, and any adapter-specific Vite plugins — adapters remain the source of truth for their own build requirements. + +## Network Switching + +Runtimes are network-scoped and immutable. To switch networks, dispose and recreate: + +```ts +let runtime = ecosystemDefinition.createRuntime('composer', ethereumMainnet); + +// User switches to Sepolia +runtime.dispose(); +runtime = ecosystemDefinition.createRuntime('composer', sepoliaTestnet); +``` + + +Wallet sessions survive network changes. Runtime disposal cleans up runtime-owned resources (listeners, subscriptions, RPC connections) but does **not** disconnect the wallet. Disconnect is always an explicit user action. + + +## Error Handling + +The adapter system uses two primary error classes: + +| Error | When it's thrown | +| --- | --- | +| `UnsupportedProfileError` | `createRuntime` is called with a profile the adapter can't fully support. The error message lists the missing capabilities. | +| `RuntimeDisposedError` | Any method or property is accessed on a disposed runtime, or an in-flight async operation is interrupted by disposal. | + +```ts +import { UnsupportedProfileError, RuntimeDisposedError } from '@openzeppelin/ui-types'; + +try { + const runtime = ecosystemDefinition.createRuntime('operator', network); +} catch (error) { + if (error instanceof UnsupportedProfileError) { + console.log('Missing capabilities:', error.message); + } +} +``` + +## Using with React + +Adapters integrate with React through three layers: a **wallet context provider** that manages wallet SDK state, **`configureUiKit`** that selects and configures the wallet UI kit, and a **hook facade** that exposes a uniform set of React hooks across every ecosystem. + +### Wallet Context Provider + +Each adapter exports a root React component via `getEcosystemReactUiContextProvider()`. This component wraps your application (or the relevant subtree) and bootstraps the wallet SDK context — for example, EVM adapters render `WagmiProvider` and `QueryClientProvider` internally, while the Stellar adapter renders its own `StellarWalletContext.Provider`. + +You don't render these providers yourself. Instead, pass the runtime to a shared `WalletStateProvider` from `@openzeppelin/ui-react`, which delegates to the correct ecosystem provider automatically: + +```tsx +import { WalletStateProvider } from '@openzeppelin/ui-react'; + +function App() { + return ( + + + + ); +} +``` + +The `loadConfigModule` callback lets the adapter lazily load user-authored wallet configuration files (e.g. `src/config/wallet/rainbowkit.config.ts` for a RainbowKit setup). If you don't use a native config file, pass a function that returns `null`. + +### Configuring the UI Kit + +Before rendering wallet UI components, configure the UI kit on the runtime's `uiKit` capability. This selects which wallet UI library to use (RainbowKit, a custom theme, or none) and passes kit-specific options: + +```ts +await runtime.uiKit?.configureUiKit( + { + kitName: 'rainbowkit', // or 'custom', 'none' + kitConfig: { + // kit-specific options, e.g., showInjectedConnector: true + }, + }, + { + loadUiKitNativeConfig: loadAppConfigModule, + } +); +``` + +After configuration, retrieve the wallet UI components from the runtime: + +```ts +const walletComponents = runtime.uiKit?.getEcosystemWalletComponents?.(); +const ConnectButton = walletComponents?.ConnectButton; +const AccountDisplay = walletComponents?.AccountDisplay; +const NetworkSwitcher = walletComponents?.NetworkSwitcher; +``` + +### Connecting and Disconnecting Wallets + +The `WalletCapability` on the runtime exposes imperative methods for wallet lifecycle management: + +```ts +// List available wallet connectors +const connectors = runtime.wallet.getAvailableConnectors(); + +// Connect using a specific connector +await runtime.wallet.connectWallet(connectors[0].id); + +// Check connection status +const status = runtime.wallet.getWalletConnectionStatus(); + +// Listen for connection changes +const unsubscribe = runtime.wallet.onWalletConnectionChange?.((newStatus) => { + console.log('Wallet status changed:', newStatus); +}); + +// Disconnect +await runtime.wallet.disconnectWallet(); +``` + + +`connectWallet` and `disconnectWallet` are imperative calls on the runtime — they are independent of the React rendering layer. You can call them from event handlers, effects, or outside React entirely. + + +### Hook Facade Pattern + +Every adapter exports a **facade hooks** object that conforms to the `EcosystemSpecificReactHooks` interface. This gives consuming applications a uniform set of React hooks regardless of the underlying wallet library. + +The EVM adapter maps its facade to [wagmi](https://wagmi.sh/) hooks, the Stellar adapter provides custom implementations, and other adapters follow the same pattern: + +```ts +// EVM facade (wraps wagmi) +import { evmFacadeHooks } from '@openzeppelin/adapter-evm'; + +// Stellar facade (custom hooks) +import { stellarFacadeHooks } from '@openzeppelin/adapter-stellar'; +``` + +Each facade object includes these hooks: + +| Hook | Purpose | +| --- | --- | +| `useAccount` | Returns the connected account address, connection status, and chain info | +| `useConnect` | Provides a `connect` function and available connectors | +| `useDisconnect` | Provides a `disconnect` function | +| `useSwitchChain` | Returns a `switchChain` function (EVM-only; stubs on other ecosystems) | +| `useChainId` | Returns the currently active chain ID | +| `useChains` | Returns the list of configured chains | +| `useBalance` | Returns the native token balance for the connected account | + +In practice, you access facade hooks through the runtime rather than importing them directly — the runtime's `uiKit.getEcosystemReactHooks()` method returns the correct facade for the active ecosystem: + +```tsx +import { useWalletState } from '@openzeppelin/ui-react'; + +function AccountInfo() { + const { walletFacadeHooks } = useWalletState(); + + const { address, isConnected } = walletFacadeHooks.useAccount(); + const { switchChain } = walletFacadeHooks.useSwitchChain(); + + if (!isConnected) return

Not connected

; + + return ( +
+

Connected: {address}

+ +
+ ); +} +``` + + +Hooks that don't apply to a given ecosystem return safe stubs. For example, the Stellar facade's `useSwitchChain` returns `{ switchChain: undefined }` — your components can feature-detect this without conditional imports. + + +## Multi-Ecosystem Apps + +Applications that interact with multiple blockchains create one runtime per ecosystem. Each runtime is independent — it manages its own wallet session, RPC connections, and lifecycle. + +### Setup + +Install the adapters you need and the shared Vite plugin: + +```bash +pnpm add @openzeppelin/adapter-evm @openzeppelin/adapter-stellar @openzeppelin/ui-types +pnpm add -D @openzeppelin/adapters-vite +``` + +Merge adapter build requirements with `defineOpenZeppelinAdapterViteConfig`: + +```ts +// vite.config.ts +import { defineOpenZeppelinAdapterViteConfig } from '@openzeppelin/adapters-vite'; +import react from '@vitejs/plugin-react'; + +export default defineOpenZeppelinAdapterViteConfig({ + ecosystems: ['evm', 'stellar'], + config: { + plugins: [react()], + }, +}); +``` + +### Creating Runtimes + +Instantiate a runtime from each adapter's `ecosystemDefinition`: + +```ts +import { ecosystemDefinition as evmDefinition } from '@openzeppelin/adapter-evm'; +import { ecosystemDefinition as stellarDefinition } from '@openzeppelin/adapter-stellar'; + +// EVM runtime — e.g., Ethereum Mainnet +const evmNetwork = evmDefinition.networks[0]; +const evmRuntime = evmDefinition.createRuntime('composer', evmNetwork); + +// Stellar runtime — e.g., Stellar Mainnet +const stellarNetwork = stellarDefinition.networks[0]; +const stellarRuntime = stellarDefinition.createRuntime('viewer', stellarNetwork); +``` + +Each runtime is fully isolated. The EVM runtime connects to Ethereum via wagmi, the Stellar runtime connects to Horizon/Soroban — they share no state and can be disposed independently: + +```ts +// Use both runtimes side by side +const evmValid = evmRuntime.addressing.isValidAddress('0x1234...'); +const stellarValid = stellarRuntime.addressing.isValidAddress('G...'); + +const evmBalance = await evmRuntime.query.queryViewFunction( + '0xToken', 'balanceOf', ['0xOwner'] +); + +// Dispose one without affecting the other +evmRuntime.dispose(); +// stellarRuntime continues working +``` + + +Wallet sessions are ecosystem-scoped, not runtime-scoped. Disposing an EVM runtime to switch from Mainnet to Sepolia does not disconnect the user's MetaMask — the wallet session persists across runtime recreation within the same ecosystem. + + +## Next Steps + +- Read the [Architecture](/ecosystem-adapters/architecture) page for a deep dive into tiers, profiles, and lifecycle management +- Explore [Supported Ecosystems](/ecosystem-adapters/supported-ecosystems) to see what each adapter provides +- Follow the [Building an Adapter](/ecosystem-adapters/building-an-adapter) guide to add support for a new chain +- Browse the [adapter source code on GitHub](https://github.com/OpenZeppelin/openzeppelin-adapters) for implementation details diff --git a/content/ecosystem-adapters/index.mdx b/content/ecosystem-adapters/index.mdx new file mode 100644 index 00000000..e6922464 --- /dev/null +++ b/content/ecosystem-adapters/index.mdx @@ -0,0 +1,71 @@ +--- +title: Ecosystem Adapters +--- + +**OpenZeppelin Ecosystem Adapters** are a set of modular, chain-specific integration packages that let applications interact with any supported blockchain through a single, unified interface. Built on 13 composable capability interfaces organized in 3 tiers, each adapter encapsulates everything needed — contract loading, type mapping, transaction execution, wallet connection, and network configuration — while keeping consuming applications completely chain-agnostic. + + +**Source code**: The adapters are open-source. Browse the implementation, open issues, and contribute at [**github.com/OpenZeppelin/openzeppelin-adapters**](https://github.com/OpenZeppelin/openzeppelin-adapters). + + + + + Understand the capability-based architecture: tiers, profiles, and the runtime lifecycle. + + + Install adapters, configure profiles, and run your first cross-chain interaction. + + + Explore the production-ready EVM, Stellar, Polkadot, and Midnight adapters. + + + Step-by-step guide to implementing a new adapter for your blockchain. + + + +## Why Adapters? + +Building cross-chain tooling traditionally forces developers into one of two traps: a monolithic abstraction that leaks chain details, or per-chain forks that drift apart over time. Adapters solve this with a **capability-based decomposition** — each chain implements only the interfaces it supports, and consuming applications pull in only what they need. + +```mermaid +flowchart LR + App["Your Application"] --> Runtime["EcosystemRuntime"] + Runtime --> T1["Tier 1 — Lightweight\n4 capabilities\nAddressing, Explorer, ..."] + Runtime --> T2["Tier 2 — Schema\n4 capabilities\nContractLoading, Query, ..."] + Runtime --> T3["Tier 3 — Runtime\n5 capabilities\nExecution, Wallet, ..."] + + style T1 fill:#e3f2fd,stroke:#1976d2,color:#000 + style T2 fill:#fff3e0,stroke:#f57c00,color:#000 + style T3 fill:#fce4ec,stroke:#c62828,color:#000 +``` + +## Key Design Principles + +- **Pay for what you use.** Tier 1 capabilities are stateless and never pull in wallet SDKs or RPC clients. Import `addressing` and you get only address validation — nothing more. +- **Profiles simplify consumption.** Five pre-composed profiles (Declarative, Viewer, Transactor, Composer, Operator) match common application archetypes so you don't have to assemble capabilities manually. +- **Adapters own their chains.** All chain-specific logic — ABI parsing, Soroban type mapping, ZK proof orchestration — stays inside the adapter package. The consuming application never touches it. +- **Runtime lifecycle is explicit.** Runtimes are immutable and network-scoped. Switching networks means disposing the old runtime and creating a new one — no hidden state mutations. + +## Packages + +| Package | Description | Status | +| --- | --- | --- | +| `@openzeppelin/adapter-evm` | Ethereum, Polygon, and EVM-compatible chains | Production | +| `@openzeppelin/adapter-stellar` | Stellar / Soroban | Production | +| `@openzeppelin/adapter-polkadot` | Polkadot Hub, Moonbeam (EVM path) | Production | +| `@openzeppelin/adapter-midnight` | Midnight Network (ZK artifacts, Lace wallet) | Production | +| `@openzeppelin/adapter-solana` | Solana (scaffolding) | In Progress | +| `@openzeppelin/adapter-evm-core` | Shared EVM capability implementations (internal) | Internal | +| `@openzeppelin/adapter-runtime-utils` | Profile composition and lifecycle utilities (internal) | Internal | +| `@openzeppelin/adapters-vite` | Shared Vite/Vitest build integration | Utility | + +## Who Uses Adapters? + +Adapters are consumed by several OpenZeppelin products: + +- [**UI Builder**](https://github.com/OpenZeppelin/ui-builder) — full-featured smart contract interaction UI +- [**OpenZeppelin UI**](https://github.com/OpenZeppelin/openzeppelin-ui) — shared UI components and React integration +- [**Role Manager**](https://github.com/OpenZeppelin/role-manager) — role and permission management tool +- [**RWA Wizard**](https://github.com/OpenZeppelin/rwa-wizard) — real-world asset token generation + +Any TypeScript application that needs chain-agnostic blockchain interaction can use adapters directly. diff --git a/content/ecosystem-adapters/supported-ecosystems.mdx b/content/ecosystem-adapters/supported-ecosystems.mdx new file mode 100644 index 00000000..f97f116d --- /dev/null +++ b/content/ecosystem-adapters/supported-ecosystems.mdx @@ -0,0 +1,149 @@ +--- +title: Supported Ecosystems +--- + +Each adapter implements the subset of the [13 capability interfaces](/ecosystem-adapters/architecture#capability-tiers) that its blockchain supports. This page summarizes what each production adapter provides. + +| Adapter | Networks | Capabilities | Status | +| --- | --- | --- | --- | +| **EVM** | Ethereum, Polygon, Arbitrum, Base, Optimism, ... | 13/13 | Production | +| **Stellar** | Stellar Public, Stellar Testnet | 13/13 | Production | +| **Polkadot** | Polkadot Hub, Moonbeam, Moonriver | 13/13 (EVM path) | Production | +| **Midnight** | Midnight Testnet | 11/13 | Production | +| **Solana** | Devnet, Testnet, Mainnet Beta | 4/13 (Tier 1 only) | Scaffolding | + +## EVM — `@openzeppelin/adapter-evm` + +The EVM adapter targets Ethereum and all EVM-compatible chains. It implements the full set of 13 capabilities and supports all 5 profiles. + +**Supported Networks**: Ethereum Mainnet, Sepolia, Polygon, Polygon Amoy, Arbitrum One, Arbitrum Sepolia, Base, Base Sepolia, Optimism, Optimism Sepolia, and more. + +**Profiles Supported**: Declarative, Viewer, Transactor, Composer, Operator + +### Highlights + +- **Contract Loading**: Fetches ABIs from Etherscan and Sourcify with automatic fallback ordering. Detects proxy contracts and resolves implementation ABIs. +- **Type Mapping**: Maps all Solidity types (`uint256`, `address`, `bytes32`, tuples, dynamic arrays) to UI-friendly form fields. +- **Execution Strategies**: Pluggable EOA (direct wallet signing via Wagmi/Viem) and OpenZeppelin Relayer strategies. +- **Wallet Integration**: Built on Wagmi and RainbowKit with React context providers and hooks. +- **Access Control**: Full role management including `grantRole`, `revokeRole`, `renounceRole`, ownership transfers, and role enumeration. + +### Configuration Resolution + +The EVM adapter resolves RPC URLs and explorer API keys through a layered priority system: + +1. **User-provided config** — settings from the end-user (stored in localStorage) +2. **Application config** — deployer settings via `app.config.json` or environment variables +3. **Default config** — values from the `NetworkConfig` the adapter was instantiated with + +### Internal Architecture + +`@openzeppelin/adapter-evm` is a thin public layer that re-exports capabilities from `@openzeppelin/adapter-evm-core`. The core package centralizes all reusable EVM implementations: + +| Core Module | Responsibility | +| --- | --- | +| ABI loading | Etherscan / Sourcify fetching, proxy detection | +| Schema transformation | ABI → `ContractSchema` conversion | +| Input/output conversion | Solidity type ↔ form field mapping | +| Query helpers | Public client creation, view function calls | +| Transaction execution | Calldata formatting, strategy dispatch | +| Wallet infrastructure | Wagmi config, RainbowKit setup, session management | +| Relayer | Relayer SDK integration, network service forms | +| Access Control | Role queries, grant/revoke operations | + +## Stellar — `@openzeppelin/adapter-stellar` + +The Stellar adapter provides a complete Soroban implementation with all 13 capabilities. + +**Supported Networks**: Stellar Public, Stellar Testnet + +**Profiles Supported**: Declarative, Viewer, Transactor, Composer, Operator + +### Highlights + +- **Contract Loading**: Loads Soroban contract specifications and transforms them into the shared `ContractSchema`. +- **Type Mapping**: Maps Soroban types (`Address`, `i128`, `Bytes`, `Vec`, `Map`) to form fields with input validation. +- **Execution**: Parses form values into Soroban `ScVal` arguments. Supports both EOA and Relayer execution strategies. +- **Wallet Integration**: Uses Stellar Wallets Kit with React UI providers and hooks. +- **Access Control**: First-class support for Ownable and AccessControl patterns, including role queries, ownership actions, and optional indexer-backed historical lookups. +- **SAC Support**: Detects Stellar Asset Contracts and dynamically loads their specifications. + +## Polkadot — `@openzeppelin/adapter-polkadot` + +The Polkadot adapter targets EVM-compatible parachains and relay chains, reusing the shared EVM core for most capabilities. + +**Supported Networks**: Polkadot Hub, Kusama Hub, Moonbeam, Moonriver + +**Profiles Supported**: Declarative, Viewer, Transactor, Composer (EVM execution path) + +### Highlights + +- **EVM Core Reuse**: Delegates ABI loading, queries, transaction execution, and wallet infrastructure to `adapter-evm-core`. +- **Polkadot Metadata**: Exposes relay chain associations and network category distinctions. +- **React Wallet Provider**: Ships wallet provider utilities for consumer applications. +- **Future Substrate Path**: Structured to support native Substrate/Wasm modules alongside the current EVM path. + + +Non-EVM execution paths (native Substrate) are not yet implemented. Requesting a profile that requires execution on a non-EVM network will throw `UnsupportedProfileError`. + + +## Midnight — `@openzeppelin/adapter-midnight` + +The Midnight adapter enables browser-based interaction with Midnight contracts using zero-knowledge proof workflows. + +**Supported Networks**: Midnight Testnet + +### Highlights + +- **Artifact Ingestion**: Supports ZIP-based artifact bundles for contract evaluation and ZK proof orchestration entirely in the browser. +- **Lace Wallet**: Integrates with the Lace wallet for signing and execution. +- **Runtime Secrets**: Handles organizer-only secrets as in-memory execution-time inputs, not persisted configuration. +- **Lazy Polyfills**: Midnight-specific browser polyfills are lazy-loaded so they don't affect other ecosystem bundles. +- **Export Bootstrap**: Supports exporting self-contained applications that bundle Midnight contract artifacts for standalone use. + +### Build Requirements + +Midnight requires host-provided plugin factories for WASM and top-level await: + +```ts +import { loadOpenZeppelinAdapterViteConfig } from '@openzeppelin/adapters-vite'; + +const adapterConfigs = await loadOpenZeppelinAdapterViteConfig({ + ecosystems: ['midnight'], + pluginFactories: { + midnight: { wasm, topLevelAwait }, + }, +}); +``` + +## Solana — `@openzeppelin/adapter-solana` + + +The Solana adapter is scaffolding only and is not yet production-ready. + + +The Solana package defines the package boundaries and network configurations for a future adapter. It currently provides: + +- Solana network configurations (Devnet, Testnet, Mainnet Beta) +- Package structure and sub-path export scaffolding +- Tier 1 capability implementations (Addressing, Explorer, NetworkCatalog, UiLabels) + +The Operator profile is explicitly unsupported — `createRuntime('operator', ...)` throws `UnsupportedProfileError` because `accessControl` factories are not yet implemented. + +## Capability Support Matrix + +| Capability | EVM | Stellar | Polkadot | Midnight | Solana | +| --- | --- | --- | --- | --- | --- | +| Addressing | ✅ | ✅ | ✅ | ✅ | ✅ | +| Explorer | ✅ | ✅ | ✅ | ✅ | ✅ | +| NetworkCatalog | ✅ | ✅ | ✅ | ✅ | ✅ | +| UiLabels | ✅ | ✅ | ✅ | ✅ | ✅ | +| ContractLoading | ✅ | ✅ | ✅ | ✅ | — | +| Schema | ✅ | ✅ | ✅ | ✅ | — | +| TypeMapping | ✅ | ✅ | ✅ | ✅ | — | +| Query | ✅ | ✅ | ✅ | ✅ | — | +| Execution | ✅ | ✅ | ✅ (EVM) | ✅ | — | +| Wallet | ✅ | ✅ | ✅ | ✅ | — | +| UiKit | ✅ | ✅ | ✅ | ✅ | — | +| Relayer | ✅ | ✅ | ✅ | — | — | +| AccessControl | ✅ | ✅ | ✅ | — | — | diff --git a/src/hooks/use-navigation-tree.ts b/src/hooks/use-navigation-tree.ts index 5c57bc00..53b96e2c 100644 --- a/src/hooks/use-navigation-tree.ts +++ b/src/hooks/use-navigation-tree.ts @@ -4,6 +4,7 @@ import { usePathname } from "next/navigation"; import { useEffect } from "react"; import { arbitrumStylusTree, + ecosystemAdaptersTree, ethereumEvmTree, impactTree, midnightTree, @@ -64,6 +65,8 @@ export function useNavigationTree() { return uniswapTree; } else if (pathname.startsWith("/substrate-runtimes")) { return polkadotTree; + } else if (pathname.startsWith("/ecosystem-adapters")) { + return ecosystemAdaptersTree; } else if (pathname.startsWith("/tools")) { return ethereumEvmTree; } diff --git a/src/navigation/arbitrum-stylus.json b/src/navigation/arbitrum-stylus.json index 0a8cd80d..0641c29d 100644 --- a/src/navigation/arbitrum-stylus.json +++ b/src/navigation/arbitrum-stylus.json @@ -612,5 +612,36 @@ "external": true } ] + }, + { + "type": "folder", + "name": "Ecosystem Adapters", + "index": { + "type": "page", + "name": "Overview", + "url": "/ecosystem-adapters" + }, + "children": [ + { + "type": "page", + "name": "Architecture", + "url": "/ecosystem-adapters/architecture" + }, + { + "type": "page", + "name": "Getting Started", + "url": "/ecosystem-adapters/getting-started" + }, + { + "type": "page", + "name": "Supported Ecosystems", + "url": "/ecosystem-adapters/supported-ecosystems" + }, + { + "type": "page", + "name": "Building an Adapter", + "url": "/ecosystem-adapters/building-an-adapter" + } + ] } ] diff --git a/src/navigation/ecosystem-adapters.json b/src/navigation/ecosystem-adapters.json new file mode 100644 index 00000000..0855ac83 --- /dev/null +++ b/src/navigation/ecosystem-adapters.json @@ -0,0 +1,27 @@ +[ + { + "type": "page", + "name": "Overview", + "url": "/ecosystem-adapters" + }, + { + "type": "page", + "name": "Architecture", + "url": "/ecosystem-adapters/architecture" + }, + { + "type": "page", + "name": "Getting Started", + "url": "/ecosystem-adapters/getting-started" + }, + { + "type": "page", + "name": "Supported Ecosystems", + "url": "/ecosystem-adapters/supported-ecosystems" + }, + { + "type": "page", + "name": "Building an Adapter", + "url": "/ecosystem-adapters/building-an-adapter" + } +] diff --git a/src/navigation/ethereum-evm.json b/src/navigation/ethereum-evm.json index 8ce1463b..09b78484 100644 --- a/src/navigation/ethereum-evm.json +++ b/src/navigation/ethereum-evm.json @@ -1246,6 +1246,37 @@ "name": "Role Manager", "url": "/role-manager" }, + { + "type": "folder", + "name": "Ecosystem Adapters", + "index": { + "type": "page", + "name": "Overview", + "url": "/ecosystem-adapters" + }, + "children": [ + { + "type": "page", + "name": "Architecture", + "url": "/ecosystem-adapters/architecture" + }, + { + "type": "page", + "name": "Getting Started", + "url": "/ecosystem-adapters/getting-started" + }, + { + "type": "page", + "name": "Supported Ecosystems", + "url": "/ecosystem-adapters/supported-ecosystems" + }, + { + "type": "page", + "name": "Building an Adapter", + "url": "/ecosystem-adapters/building-an-adapter" + } + ] + }, { "type": "folder", "name": "Defender", diff --git a/src/navigation/index.ts b/src/navigation/index.ts index eb8ac81b..35a30ea7 100644 --- a/src/navigation/index.ts +++ b/src/navigation/index.ts @@ -1,4 +1,5 @@ import arbitrumStylusData from "./arbitrum-stylus.json"; +import ecosystemAdaptersData from "./ecosystem-adapters.json"; import ethereumEvmData from "./ethereum-evm.json"; import impactData from "./impact.json"; import midnightData from "./midnight.json"; @@ -21,6 +22,7 @@ const zama = zamaData as NavigationNode[]; const uniswap = uniswapData as NavigationNode[]; const polkadot = polkadotData as NavigationNode[]; const impact = impactData as NavigationNode[]; +const ecosystemAdapters = ecosystemAdaptersData as NavigationNode[]; // Create separate navigation trees for each blockchain export const ethereumEvmTree: NavigationTree = { @@ -73,4 +75,9 @@ export const impactTree: NavigationTree = { children: impact, }; +export const ecosystemAdaptersTree: NavigationTree = { + name: "Ecosystem Adapters", + children: ecosystemAdapters, +}; + export * from "./types"; diff --git a/src/navigation/midnight.json b/src/navigation/midnight.json index d1411012..f782f069 100644 --- a/src/navigation/midnight.json +++ b/src/navigation/midnight.json @@ -118,5 +118,40 @@ "url": "/contracts-compact/api/utils" } ] + }, + { + "type": "separator", + "name": "Open Source Tools" + }, + { + "type": "folder", + "name": "Ecosystem Adapters", + "index": { + "type": "page", + "name": "Overview", + "url": "/ecosystem-adapters" + }, + "children": [ + { + "type": "page", + "name": "Architecture", + "url": "/ecosystem-adapters/architecture" + }, + { + "type": "page", + "name": "Getting Started", + "url": "/ecosystem-adapters/getting-started" + }, + { + "type": "page", + "name": "Supported Ecosystems", + "url": "/ecosystem-adapters/supported-ecosystems" + }, + { + "type": "page", + "name": "Building an Adapter", + "url": "/ecosystem-adapters/building-an-adapter" + } + ] } ] diff --git a/src/navigation/polkadot.json b/src/navigation/polkadot.json index 66c48e4c..29140d70 100644 --- a/src/navigation/polkadot.json +++ b/src/navigation/polkadot.json @@ -745,5 +745,36 @@ "external": true } ] + }, + { + "type": "folder", + "name": "Ecosystem Adapters", + "index": { + "type": "page", + "name": "Overview", + "url": "/ecosystem-adapters" + }, + "children": [ + { + "type": "page", + "name": "Architecture", + "url": "/ecosystem-adapters/architecture" + }, + { + "type": "page", + "name": "Getting Started", + "url": "/ecosystem-adapters/getting-started" + }, + { + "type": "page", + "name": "Supported Ecosystems", + "url": "/ecosystem-adapters/supported-ecosystems" + }, + { + "type": "page", + "name": "Building an Adapter", + "url": "/ecosystem-adapters/building-an-adapter" + } + ] } ] diff --git a/src/navigation/stellar.json b/src/navigation/stellar.json index be1994a8..f813898f 100644 --- a/src/navigation/stellar.json +++ b/src/navigation/stellar.json @@ -644,5 +644,36 @@ "type": "page", "name": "Role Manager", "url": "/role-manager" + }, + { + "type": "folder", + "name": "Ecosystem Adapters", + "index": { + "type": "page", + "name": "Overview", + "url": "/ecosystem-adapters" + }, + "children": [ + { + "type": "page", + "name": "Architecture", + "url": "/ecosystem-adapters/architecture" + }, + { + "type": "page", + "name": "Getting Started", + "url": "/ecosystem-adapters/getting-started" + }, + { + "type": "page", + "name": "Supported Ecosystems", + "url": "/ecosystem-adapters/supported-ecosystems" + }, + { + "type": "page", + "name": "Building an Adapter", + "url": "/ecosystem-adapters/building-an-adapter" + } + ] } ]