|
| 1 | +--- |
| 2 | +title: Architecture |
| 3 | +--- |
| 4 | + |
| 5 | +OpenZeppelin UIKit is built as a layered stack of independently installable packages. This page explains how those layers fit together, how the capability-driven adapter model works, and how runtimes manage lifecycle across multiple ecosystems. |
| 6 | + |
| 7 | +## Package Layers |
| 8 | + |
| 9 | +The packages form a dependency chain where each layer builds on the ones below it. Lower layers are lighter and more generic; higher layers add React-specific and domain-specific behavior. |
| 10 | + |
| 11 | +```mermaid |
| 12 | +%%{init: {'flowchart': {'nodeSpacing': 20, 'rankSpacing': 40}} }%% |
| 13 | +flowchart TD |
| 14 | + Storage(["7 · ui-storage"]) |
| 15 | + Renderer(["6 · ui-renderer"]) |
| 16 | + ReactPkg(["5 · ui-react"]) |
| 17 | + Components(["4 · ui-components"]) |
| 18 | + Styles(["3 · ui-styles"]) |
| 19 | + Utils(["2 · ui-utils"]) |
| 20 | + Types(["1 · ui-types"]) |
| 21 | +
|
| 22 | + Storage --> Utils |
| 23 | + Renderer --> Components |
| 24 | + ReactPkg --> Components |
| 25 | + Components --> Styles |
| 26 | + Components --> Utils |
| 27 | + Utils --> Types |
| 28 | +
|
| 29 | + style Storage fill:#e8eaf6,stroke:#5c6bc0,color:#1a237e |
| 30 | + style Renderer fill:#e8eaf6,stroke:#5c6bc0,color:#1a237e |
| 31 | + style ReactPkg fill:#e8eaf6,stroke:#5c6bc0,color:#1a237e |
| 32 | + style Components fill:#e0f2f1,stroke:#26a69a,color:#004d40 |
| 33 | + style Styles fill:#e0f2f1,stroke:#26a69a,color:#004d40 |
| 34 | + style Utils fill:#fff3e0,stroke:#ff9800,color:#e65100 |
| 35 | + style Types fill:#fff3e0,stroke:#ff9800,color:#e65100 |
| 36 | +``` |
| 37 | + |
| 38 | +**Color key:** <span style={{color: '#5c6bc0'}}>■</span> Application layers (5–7) · <span style={{color: '#26a69a'}}>■</span> UI & design (3–4) · <span style={{color: '#ff9800'}}>■</span> Foundation (1–2) |
| 39 | + |
| 40 | +| Layer | Package | Responsibility | |
| 41 | +| --- | --- | --- | |
| 42 | +| 1 | `@openzeppelin/ui-types` | TypeScript interfaces for capabilities, schemas, form models, networks, transactions, and execution config. No runtime code: pure type definitions. | |
| 43 | +| 2 | `@openzeppelin/ui-utils` | Framework-agnostic helpers: `AppConfigService` for environment/config loading, structured logger, validation utilities, and routing helpers. | |
| 44 | +| 3 | `@openzeppelin/ui-styles` | Tailwind CSS 4 theme tokens using OKLCH color space. Ships CSS variables and custom variants (dark mode). No JavaScript. | |
| 45 | +| 4 | `@openzeppelin/ui-components` | React UI primitives (buttons, dialogs, cards, tabs) and blockchain-aware form fields (address, amount, bytes, enum, map). Built on Radix UI + shadcn/ui patterns. | |
| 46 | +| 5 | `@openzeppelin/ui-react` | `RuntimeProvider` for managing `EcosystemRuntime` instances per network. `WalletStateProvider` for global wallet state. Derived hooks for cross-ecosystem wallet abstraction. | |
| 47 | +| 6 | `@openzeppelin/ui-renderer` | `TransactionForm` for schema-driven transaction forms. `ContractStateWidget` for view function queries. `ExecutionConfigDisplay`, `AddressBookWidget`, address book components. | |
| 48 | +| 7 | `@openzeppelin/ui-storage` | `EntityStorage` and `KeyValueStorage` base classes on Dexie.js/IndexedDB. Account alias plugin for address-to-name mapping. | |
| 49 | + |
| 50 | +<a id="capabilities"></a> |
| 51 | +## Capabilities |
| 52 | + |
| 53 | +The UIKit type system defines 13 **capabilities**: small, focused interfaces that describe what an adapter can do. |
| 54 | + |
| 55 | +Capabilities are organized into three tiers based on their requirements: |
| 56 | + |
| 57 | +```mermaid |
| 58 | +%%{init: {'flowchart': {'nodeSpacing': 30, 'rankSpacing': 30}} }%% |
| 59 | +flowchart TD |
| 60 | + T1["<b>Tier 1 (Lightweight)</b><br/>Addressing · Explorer · NetworkCatalog · UiLabels"] |
| 61 | + T2["<b>Tier 2 (Network-Aware)</b><br/>ContractLoading · Schema · TypeMapping · Query"] |
| 62 | + T3["<b>Tier 3 (Stateful)</b><br/>Execution · Wallet · UiKit · Relayer · AccessControl"] |
| 63 | +
|
| 64 | + T1 --"may import"--> T2 --"may import"--> T3 |
| 65 | +
|
| 66 | + style T1 fill:#e8eaf6,stroke:#5c6bc0,color:#000 |
| 67 | + style T2 fill:#e0f2f1,stroke:#26a69a,color:#000 |
| 68 | + style T3 fill:#fff3e0,stroke:#ff9800,color:#000 |
| 69 | +``` |
| 70 | + |
| 71 | +**Tier 1** requires no runtime context: safe to import anywhere. **Tier 2** needs a `networkConfig`. **Tier 3** additionally needs wallet state and participates in the `dispose()` lifecycle. Each higher tier may import from lower tiers, but never the reverse. |
| 72 | + |
| 73 | +| Capability | Tier | Purpose | |
| 74 | +| --- | --- | --- | |
| 75 | +| `Addressing` | 1 | Address validation, formatting, checksumming | |
| 76 | +| `Explorer` | 1 | Block explorer URL generation | |
| 77 | +| `NetworkCatalog` | 1 | Available network listing and metadata | |
| 78 | +| `UiLabels` | 1 | Human-readable labels for ecosystem-specific terms | |
| 79 | +| `ContractLoading` | 2 | Fetch and parse contract ABIs/IDLs | |
| 80 | +| `Schema` | 2 | Transform contract definitions into form-renderable schemas | |
| 81 | +| `TypeMapping` | 2 | Map blockchain types (e.g. `uint256`) to form field types | |
| 82 | +| `Query` | 2 | Execute read-only contract calls (view functions) | |
| 83 | +| `Execution` | 3 | Sign, broadcast, and track transactions | |
| 84 | +| `Wallet` | 3 | Connect/disconnect wallets, account state, chain switching | |
| 85 | +| `UiKit` | 3 | Ecosystem-specific React components and hooks | |
| 86 | +| `Relayer` | 3 | Gas-sponsored transaction execution via relayers | |
| 87 | +| `AccessControl` | 3 | Role-based access control queries and snapshots | |
| 88 | + |
| 89 | +### Capability Bundles |
| 90 | + |
| 91 | +Higher-level components request specific **bundles** of capabilities rather than the full set. For example, `TransactionForm` expects a `TransactionFormCapabilities` type: an intersection of the capabilities needed for form rendering, execution, and status tracking. |
| 92 | + |
| 93 | +This means you can pass a partial adapter that only implements what the component actually needs. |
| 94 | + |
| 95 | +<a id="runtimes"></a> |
| 96 | +## Runtimes and Profiles |
| 97 | + |
| 98 | +### Ecosystem Runtimes |
| 99 | + |
| 100 | +An `EcosystemRuntime` is a live instance that bundles capabilities for a specific network. Capabilities created within the same runtime share runtime-scoped state (network config, wallet connection, caches) and are disposed together. |
| 101 | + |
| 102 | +Runtimes are created by ecosystem adapter packages: |
| 103 | + |
| 104 | +```tsx |
| 105 | +import { ecosystemDefinition } from '@openzeppelin/adapter-evm'; |
| 106 | + |
| 107 | +const runtime = await ecosystemDefinition.createRuntime( |
| 108 | + 'composer', // profile name |
| 109 | + ethereumMainnetConfig // network config |
| 110 | +); |
| 111 | + |
| 112 | +// Access capabilities from the runtime |
| 113 | +const address = runtime.addressing.formatAddress('0x...'); |
| 114 | +const schema = await runtime.schema.generateFormSchema(contractDef); |
| 115 | +const txHash = await runtime.execution.signAndBroadcast(txData, execConfig); |
| 116 | + |
| 117 | +// Clean up when done |
| 118 | +runtime.dispose(); |
| 119 | +``` |
| 120 | + |
| 121 | +### Profiles |
| 122 | + |
| 123 | +Adapters support five standard profiles that define which capabilities are included: |
| 124 | + |
| 125 | +| Profile | Use Case | Tier 1 | Tier 2 | Tier 3 | |
| 126 | +| --- | --- | --- | --- | --- | |
| 127 | +| `declarative` | Address formatting, explorer links | ✓ | - | - | |
| 128 | +| `viewer` | Read contract state, no wallet needed | ✓ | ✓ | - | |
| 129 | +| `transactor` | Execute transactions, basic wallet | ✓ | ✓ | Execution, Wallet | |
| 130 | +| `composer` | Full UI with form rendering | ✓ | ✓ | ✓ (most) | |
| 131 | +| `operator` | Administrative tools, access control | ✓ | ✓ | ✓ (all) | |
| 132 | + |
| 133 | +Choose the lightest profile that fits your use case. A dashboard that only displays contract state can use `viewer`; a full transaction builder should use `composer` or `operator`. |
| 134 | + |
| 135 | +### Runtime Lifecycle |
| 136 | + |
| 137 | +```mermaid |
| 138 | +sequenceDiagram |
| 139 | + participant App as Application |
| 140 | + participant RP as RuntimeProvider |
| 141 | + participant Adapter as Ecosystem Adapter |
| 142 | +
|
| 143 | + App->>RP: Mount with resolveRuntime |
| 144 | + App->>RP: setActiveNetworkId("ethereum-mainnet") |
| 145 | + RP->>Adapter: createRuntime("composer", networkConfig) |
| 146 | + Adapter-->>RP: EcosystemRuntime |
| 147 | +
|
| 148 | + Note over RP: Cached by network ID |
| 149 | +
|
| 150 | + App->>RP: setActiveNetworkId("polygon-mainnet") |
| 151 | + RP->>Adapter: createRuntime("composer", polygonConfig) |
| 152 | + Adapter-->>RP: New EcosystemRuntime |
| 153 | +
|
| 154 | + Note over RP: Both runtimes cached |
| 155 | +
|
| 156 | + App->>RP: Unmount |
| 157 | + RP->>RP: dispose() all cached runtimes |
| 158 | +``` |
| 159 | + |
| 160 | +`RuntimeProvider` maintains a **per-network-id registry** of runtimes. When a network is selected for the first time, the runtime is created asynchronously and cached. On unmount, all runtimes are disposed, releasing any wallet connections, subscriptions, or internal state. |
| 161 | + |
| 162 | +## Execution Strategies |
| 163 | + |
| 164 | +The execution system supports multiple ways to submit a transaction. The adapter selects the appropriate strategy based on the `ExecutionConfig`: |
| 165 | + |
| 166 | +```mermaid |
| 167 | +flowchart LR |
| 168 | + Form["TransactionForm"] --> Exec["ExecutionCapability"] |
| 169 | + Exec --> Strategy{"Method?"} |
| 170 | + Strategy -->|EOA| EOA["Direct wallet signing"] |
| 171 | + Strategy -->|Relayer| Relay["Gas-sponsored via Relayer"] |
| 172 | + EOA --> Chain["Blockchain"] |
| 173 | + Relay --> Chain |
| 174 | +``` |
| 175 | + |
| 176 | +Each ecosystem adapter defines which execution methods it supports. The EVM adapter supports both EOA and Relayer; other ecosystems may support only EOA. |
| 177 | + |
| 178 | +## How It Connects |
| 179 | + |
| 180 | +Putting it all together, here is how a typical application uses UIKit with an Ecosystem Adapter: |
| 181 | + |
| 182 | +```mermaid |
| 183 | +%%{init: {'flowchart': {'nodeSpacing': 20, 'rankSpacing': 40}} }%% |
| 184 | +flowchart TD |
| 185 | + App(["Your React App"]) |
| 186 | + RP(["RuntimeProvider"]) |
| 187 | + WSP(["WalletStateProvider"]) |
| 188 | + TF(["TransactionForm"]) |
| 189 | + CSW(["ContractStateWidget"]) |
| 190 | + AdapterEVM(["adapter-evm"]) |
| 191 | + AdapterStellar(["adapter-stellar"]) |
| 192 | + Ethereum(["Ethereum"]) |
| 193 | + Stellar(["Stellar"]) |
| 194 | +
|
| 195 | + App --> RP --> WSP |
| 196 | + WSP --> TF & CSW |
| 197 | + RP -.->|createRuntime| AdapterEVM & AdapterStellar |
| 198 | + AdapterEVM --> Ethereum |
| 199 | + AdapterStellar --> Stellar |
| 200 | +
|
| 201 | + style App fill:#e8eaf6,stroke:#5c6bc0,color:#1a237e |
| 202 | + style RP fill:#e0f2f1,stroke:#26a69a,color:#004d40 |
| 203 | + style WSP fill:#e0f2f1,stroke:#26a69a,color:#004d40 |
| 204 | + style TF fill:#e0f2f1,stroke:#26a69a,color:#004d40 |
| 205 | + style CSW fill:#e0f2f1,stroke:#26a69a,color:#004d40 |
| 206 | + style AdapterEVM fill:#fff3e0,stroke:#ff9800,color:#e65100 |
| 207 | + style AdapterStellar fill:#fff3e0,stroke:#ff9800,color:#e65100 |
| 208 | + style Ethereum fill:#fce4ec,stroke:#e91e63,color:#880e4f |
| 209 | + style Stellar fill:#fce4ec,stroke:#e91e63,color:#880e4f |
| 210 | +``` |
| 211 | + |
| 212 | +## Next Steps |
| 213 | + |
| 214 | +- [Components](/tools/uikit/components): Explore all available UI primitives and form fields |
| 215 | +- [React Integration](/tools/uikit/react-integration): Deep dive into providers, hooks, and wallet state |
| 216 | +- [Building an adapter](/ecosystem-adapters/building-an-adapter): Background on the adapter pattern and ecosystem integrations |
0 commit comments