Skip to content

Commit b55bff2

Browse files
authored
Merge pull request #3 from pasevin/cursor/ecosystem-adapters-documentation-27d3
feat(ecosystem-adapters): add React integration and multi-ecosystem documentation
2 parents ed372e5 + 38ec4f5 commit b55bff2

2 files changed

Lines changed: 205 additions & 0 deletions

File tree

content/ecosystem-adapters/getting-started.mdx

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,209 @@ try {
184184
}
185185
```
186186

187+
## Using with React
188+
189+
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.
190+
191+
### Wallet Context Provider
192+
193+
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`.
194+
195+
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:
196+
197+
```tsx
198+
import { WalletStateProvider } from '@openzeppelin/ui-react';
199+
200+
function App() {
201+
return (
202+
<WalletStateProvider
203+
runtime={runtime}
204+
loadConfigModule={loadAppConfigModule}
205+
>
206+
<YourApp />
207+
</WalletStateProvider>
208+
);
209+
}
210+
```
211+
212+
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`.
213+
214+
### Configuring the UI Kit
215+
216+
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:
217+
218+
```ts
219+
await runtime.uiKit?.configureUiKit(
220+
{
221+
kitName: 'rainbowkit', // or 'custom', 'none'
222+
kitConfig: {
223+
// kit-specific options, e.g., showInjectedConnector: true
224+
},
225+
},
226+
{
227+
loadUiKitNativeConfig: loadAppConfigModule,
228+
}
229+
);
230+
```
231+
232+
After configuration, retrieve the wallet UI components from the runtime:
233+
234+
```ts
235+
const walletComponents = runtime.uiKit?.getEcosystemWalletComponents?.();
236+
const ConnectButton = walletComponents?.ConnectButton;
237+
const AccountDisplay = walletComponents?.AccountDisplay;
238+
const NetworkSwitcher = walletComponents?.NetworkSwitcher;
239+
```
240+
241+
### Connecting and Disconnecting Wallets
242+
243+
The `WalletCapability` on the runtime exposes imperative methods for wallet lifecycle management:
244+
245+
```ts
246+
// List available wallet connectors
247+
const connectors = runtime.wallet.getAvailableConnectors();
248+
249+
// Connect using a specific connector
250+
await runtime.wallet.connectWallet(connectors[0].id);
251+
252+
// Check connection status
253+
const status = runtime.wallet.getWalletConnectionStatus();
254+
255+
// Listen for connection changes
256+
const unsubscribe = runtime.wallet.onWalletConnectionChange?.((newStatus) => {
257+
console.log('Wallet status changed:', newStatus);
258+
});
259+
260+
// Disconnect
261+
await runtime.wallet.disconnectWallet();
262+
```
263+
264+
<Callout type="info">
265+
`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.
266+
</Callout>
267+
268+
### Hook Facade Pattern
269+
270+
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.
271+
272+
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:
273+
274+
```ts
275+
// EVM facade (wraps wagmi)
276+
import { evmFacadeHooks } from '@openzeppelin/adapter-evm';
277+
278+
// Stellar facade (custom hooks)
279+
import { stellarFacadeHooks } from '@openzeppelin/adapter-stellar';
280+
```
281+
282+
Each facade object includes these hooks:
283+
284+
| Hook | Purpose |
285+
| --- | --- |
286+
| `useAccount` | Returns the connected account address, connection status, and chain info |
287+
| `useConnect` | Provides a `connect` function and available connectors |
288+
| `useDisconnect` | Provides a `disconnect` function |
289+
| `useSwitchChain` | Returns a `switchChain` function (EVM-only; stubs on other ecosystems) |
290+
| `useChainId` | Returns the currently active chain ID |
291+
| `useChains` | Returns the list of configured chains |
292+
| `useBalance` | Returns the native token balance for the connected account |
293+
294+
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:
295+
296+
```tsx
297+
import { useWalletState } from '@openzeppelin/ui-react';
298+
299+
function AccountInfo() {
300+
const { walletFacadeHooks } = useWalletState();
301+
302+
const { address, isConnected } = walletFacadeHooks.useAccount();
303+
const { switchChain } = walletFacadeHooks.useSwitchChain();
304+
305+
if (!isConnected) return <p>Not connected</p>;
306+
307+
return (
308+
<div>
309+
<p>Connected: {address}</p>
310+
<button onClick={() => switchChain?.({ chainId: 11155111 })}>
311+
Switch to Sepolia
312+
</button>
313+
</div>
314+
);
315+
}
316+
```
317+
318+
<Callout type="info">
319+
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.
320+
</Callout>
321+
322+
## Multi-Ecosystem Apps
323+
324+
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.
325+
326+
### Setup
327+
328+
Install the adapters you need and the shared Vite plugin:
329+
330+
```bash
331+
pnpm add @openzeppelin/adapter-evm @openzeppelin/adapter-stellar @openzeppelin/ui-types
332+
pnpm add -D @openzeppelin/adapters-vite
333+
```
334+
335+
Merge adapter build requirements with `defineOpenZeppelinAdapterViteConfig`:
336+
337+
```ts
338+
// vite.config.ts
339+
import { defineOpenZeppelinAdapterViteConfig } from '@openzeppelin/adapters-vite';
340+
import react from '@vitejs/plugin-react';
341+
342+
export default defineOpenZeppelinAdapterViteConfig({
343+
ecosystems: ['evm', 'stellar'],
344+
config: {
345+
plugins: [react()],
346+
},
347+
});
348+
```
349+
350+
### Creating Runtimes
351+
352+
Instantiate a runtime from each adapter's `ecosystemDefinition`:
353+
354+
```ts
355+
import { ecosystemDefinition as evmDefinition } from '@openzeppelin/adapter-evm';
356+
import { ecosystemDefinition as stellarDefinition } from '@openzeppelin/adapter-stellar';
357+
358+
// EVM runtime — e.g., Ethereum Mainnet
359+
const evmNetwork = evmDefinition.networks[0];
360+
const evmRuntime = evmDefinition.createRuntime('composer', evmNetwork);
361+
362+
// Stellar runtime — e.g., Stellar Mainnet
363+
const stellarNetwork = stellarDefinition.networks[0];
364+
const stellarRuntime = stellarDefinition.createRuntime('viewer', stellarNetwork);
365+
```
366+
367+
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:
368+
369+
```ts
370+
// Use both runtimes side by side
371+
const evmValid = evmRuntime.addressing.isValidAddress('0x1234...');
372+
const stellarValid = stellarRuntime.addressing.isValidAddress('G...');
373+
374+
const evmBalance = await evmRuntime.query.queryViewFunction(
375+
'0xToken', 'balanceOf', ['0xOwner']
376+
);
377+
378+
// Dispose one without affecting the other
379+
evmRuntime.dispose();
380+
// stellarRuntime continues working
381+
```
382+
383+
<Callout type="info">
384+
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.
385+
</Callout>
386+
187387
## Next Steps
188388

189389
- Read the [Architecture](/ecosystem-adapters/architecture) page for a deep dive into tiers, profiles, and lifecycle management
190390
- Explore [Supported Ecosystems](/ecosystem-adapters/supported-ecosystems) to see what each adapter provides
191391
- Follow the [Building an Adapter](/ecosystem-adapters/building-an-adapter) guide to add support for a new chain
392+
- Browse the [adapter source code on GitHub](https://github.com/OpenZeppelin/openzeppelin-adapters) for implementation details

content/ecosystem-adapters/index.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ title: Ecosystem Adapters
44

55
**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.
66

7+
<Callout type="info">
8+
**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).
9+
</Callout>
10+
711
<Cards>
812
<Card href="/ecosystem-adapters/architecture" title="Architecture">
913
Understand the capability-based architecture: tiers, profiles, and the runtime lifecycle.

0 commit comments

Comments
 (0)