Skip to content

Commit 11febce

Browse files
authored
Merge pull request #142 from pasevin/cursor/ecosystem-adapters-documentation-000c
docs(ecosystem-adapters): add comprehensive documentation for OpenZeppelin Ecosystem Adapters
2 parents 06f385b + 58a41d2 commit 11febce

12 files changed

Lines changed: 1389 additions & 23 deletions

File tree

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
---
2+
title: Architecture
3+
---
4+
5+
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 build your own adapter if you need to.
6+
7+
## Package Topology
8+
9+
The adapter system is split across several packages with clear dependency boundaries:
10+
11+
```mermaid
12+
flowchart TD
13+
App["Consumer Application"] --> Runtime["EcosystemRuntime"]
14+
Runtime --> Caps["Capability Interfaces\n(@openzeppelin/ui-types)"]
15+
16+
Caps --> Evm["adapter-evm"]
17+
Caps --> Polkadot["adapter-polkadot"]
18+
Caps --> Stellar["adapter-stellar"]
19+
Caps --> Midnight["adapter-midnight"]
20+
Caps --> Solana["adapter-solana"]
21+
22+
App --> Vite["adapters-vite"]
23+
24+
Evm --> Core["adapter-evm-core"]
25+
Polkadot --> Core
26+
Core --> Utils["adapter-runtime-utils"]
27+
Stellar --> Utils
28+
29+
style Caps fill:#e8eaf6,stroke:#3f51b5,color:#000
30+
style Utils fill:#e0f2f1,stroke:#00897b,color:#000
31+
style Core fill:#fff3e0,stroke:#ef6c00,color:#000
32+
```
33+
34+
- **`@openzeppelin/ui-types`** defines all 13 capability interfaces. It is the single source of truth.
35+
- **`adapter-runtime-utils`** provides profile composition, lazy capability instantiation, and staged disposal.
36+
- **`adapter-evm-core`** centralizes reusable EVM implementations shared by `adapter-evm` and `adapter-polkadot`.
37+
- Each public adapter exposes an `ecosystemDefinition` conforming to `EcosystemExport`.
38+
39+
## Capability Tiers
40+
41+
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.
42+
43+
| Tier | Category | Network | Wallet | Capabilities |
44+
| --- | --- | --- | --- | --- |
45+
| **1** | Lightweight | No | No | `Addressing`, `Explorer`, `NetworkCatalog`, `UiLabels` |
46+
| **2** | Schema | Yes | No | `ContractLoading`, `Schema`, `TypeMapping`, `Query` |
47+
| **3** | Runtime | Yes | Yes | `Execution`, `Wallet`, `UiKit`, `Relayer`, `AccessControl` |
48+
49+
### Tier Import Rules
50+
51+
Tier isolation is enforced physically through sub-path exports, not tree-shaking:
52+
53+
- **Tier 1** modules must not import from Tier 2 or Tier 3 modules
54+
- **Tier 2** modules may import from Tier 1
55+
- **Tier 3** modules may import from Tier 1 and Tier 2
56+
57+
This means importing `@openzeppelin/adapter-evm/addressing` will never pull in wallet SDKs, RPC clients, or access control code, regardless of your bundler configuration.
58+
59+
### Capability Reference
60+
61+
| Capability | Interface | Tier | Key Methods |
62+
| --- | --- | --- | --- |
63+
| Addressing | `AddressingCapability` | 1 | `isValidAddress` |
64+
| Explorer | `ExplorerCapability` | 1 | `getExplorerUrl`, `getExplorerTxUrl` |
65+
| NetworkCatalog | `NetworkCatalogCapability` | 1 | `getNetworks` |
66+
| UiLabels | `UiLabelsCapability` | 1 | `getUiLabels` |
67+
| ContractLoading | `ContractLoadingCapability` | 2 | `loadContract`, `getContractDefinitionInputs` |
68+
| Schema | `SchemaCapability` | 2 | `isViewFunction`, `getWritableFunctions` |
69+
| TypeMapping | `TypeMappingCapability` | 2 | `mapParameterTypeToFieldType`, `getTypeMappingInfo` |
70+
| Query | `QueryCapability` | 2 | `queryViewFunction`, `formatFunctionResult`, `getCurrentBlock` |
71+
| Execution | `ExecutionCapability` | 3 | `signAndBroadcast`, `formatTransactionData`, `validateExecutionConfig` |
72+
| Wallet | `WalletCapability` | 3 | `connectWallet`, `disconnectWallet`, `getWalletConnectionStatus` |
73+
| UiKit | `UiKitCapability` | 3 | `getAvailableUiKits`, `configureUiKit` |
74+
| Relayer | `RelayerCapability` | 3 | `getRelayers`, `getNetworkServiceForms` |
75+
| AccessControl | `AccessControlCapability` | 3 | `registerContract`, `grantRole`, and 17 more |
76+
77+
## Profiles
78+
79+
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`.
80+
81+
Each profile is a strict superset of Declarative. Higher profiles add capabilities incrementally:
82+
83+
### Profile-Capability Matrix
84+
85+
| Capability | Declarative | Viewer | Transactor | Composer | Operator |
86+
| --- | --- | --- | --- | --- | --- |
87+
| `Addressing` ||||||
88+
| `Explorer` ||||||
89+
| `NetworkCatalog` ||||||
90+
| `UiLabels` ||||||
91+
| `ContractLoading` | |||||
92+
| `Schema` | |||||
93+
| `TypeMapping` | |||||
94+
| `Query` | || |||
95+
| `Execution` | | ||||
96+
| `Wallet` | | ||||
97+
| `UiKit` | | | |||
98+
| `Relayer` | | | || |
99+
| `AccessControl` | | | | ||
100+
101+
### Profile Selection Guide
102+
103+
| If your application needs to… | Choose |
104+
| --- | --- |
105+
| Validate addresses, list networks, link to explorers | **Declarative** |
106+
| Read contract state without sending transactions | **Viewer** |
107+
| Send transactions without reading contract state first | **Transactor** |
108+
| Build full contract interaction UIs with relayer support | **Composer** |
109+
| Manage contract roles and permissions | **Operator** |
110+
111+
## Runtime Lifecycle
112+
113+
Runtimes are **immutable** and **network-scoped**. When a user switches networks, the consuming application must dispose the current runtime and create a new one.
114+
115+
```mermaid
116+
sequenceDiagram
117+
participant App as Application
118+
participant ES as EcosystemExport
119+
participant RT as EcosystemRuntime
120+
participant Cap as Capabilities
121+
122+
App->>ES: createRuntime('composer', networkA)
123+
ES->>RT: Compose capabilities with shared state
124+
RT-->>App: runtime (immutable)
125+
126+
App->>RT: runtime.query.queryViewFunction(...)
127+
RT->>Cap: Lazy-init Query capability
128+
Cap-->>RT: result
129+
130+
Note over App,RT: User switches to networkB
131+
132+
App->>RT: runtime.dispose()
133+
RT->>Cap: Staged cleanup (listeners → subscriptions → capabilities → RPC)
134+
App->>ES: createRuntime('composer', networkB)
135+
ES->>RT: New runtime with fresh state
136+
RT-->>App: newRuntime
137+
```
138+
139+
### Dispose Contract
140+
141+
- `dispose()` is **idempotent**: calling it multiple times is a no-op
142+
- After `dispose()`, any method or property access throws `RuntimeDisposedError`
143+
- Pending async operations (e.g., in-flight `signAndBroadcast`) are rejected with `RuntimeDisposedError`
144+
- Cleanup follows a staged order: mark disposed → reject pending operations → clean up listeners and subscriptions → dispose capabilities → release wallet and RPC resources
145+
- Runtime disposal does **not** disconnect the wallet. Disconnect is always an explicit user action
146+
147+
## Execution Strategies
148+
149+
The Execution capability uses a **strategy pattern** to support multiple transaction submission methods. Each adapter can provide its own set of strategies.
150+
151+
```mermaid
152+
flowchart TD
153+
Exec["ExecutionCapability\nsignAndBroadcast()"] --> Config{"executionConfig.method"}
154+
Config -->|"EOA"| EOA["EoaExecutionStrategy\nDirect wallet signing"]
155+
Config -->|"Relayer"| Relay["RelayerExecutionStrategy\nOpenZeppelin Relayer"]
156+
157+
EOA --> Wallet["Wallet signs tx"]
158+
Relay --> RelayerSvc["Relayer service submits tx"]
159+
160+
Wallet --> Confirm["waitForTransactionConfirmation()"]
161+
RelayerSvc --> Confirm
162+
163+
style Exec fill:#e8eaf6,stroke:#3f51b5,color:#000
164+
style EOA fill:#e8f5e9,stroke:#388e3c,color:#000
165+
style Relay fill:#fff3e0,stroke:#f57c00,color:#000
166+
```
167+
168+
The EVM and Stellar adapters ship with both EOA and Relayer strategies. Adapter authors can implement custom strategies by conforming to the `AdapterExecutionStrategy` interface.
169+
170+
## Sub-Path Exports
171+
172+
Each adapter publishes every implemented capability and profile as a dedicated sub-path export:
173+
174+
```ts
175+
// Tier 1: no wallet, no RPC, no heavy dependencies
176+
import { createAddressing } from '@openzeppelin/adapter-stellar/addressing';
177+
import { createExplorer } from '@openzeppelin/adapter-stellar/explorer';
178+
179+
// Tier 2: network-aware
180+
import { createQuery } from '@openzeppelin/adapter-stellar/query';
181+
182+
// Tier 3: wallet-dependent
183+
import { createExecution } from '@openzeppelin/adapter-stellar/execution';
184+
185+
// Profile runtimes
186+
import { createRuntime } from '@openzeppelin/adapter-stellar/profiles/composer';
187+
188+
// Metadata and networks
189+
import { networks } from '@openzeppelin/adapter-stellar/networks';
190+
```
191+
192+
This structure ensures that a Declarative-profile consumer never bundles wallet SDKs, and that individual capabilities can be tested in isolation.

0 commit comments

Comments
 (0)