Join our community of builders on

Telegram!Telegram

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 build your own adapter if you need to.

Package Topology

The adapter system is split across several packages with clear dependency boundaries:

  • @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.

TierCategoryNetworkWalletCapabilities
1LightweightNoNoAddressing, Explorer, NetworkCatalog, UiLabels
2SchemaYesNoContractLoading, Schema, TypeMapping, Query
3RuntimeYesYesExecution, 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

CapabilityInterfaceTierKey Methods
AddressingAddressingCapability1isValidAddress
ExplorerExplorerCapability1getExplorerUrl, getExplorerTxUrl
NetworkCatalogNetworkCatalogCapability1getNetworks
UiLabelsUiLabelsCapability1getUiLabels
ContractLoadingContractLoadingCapability2loadContract, getContractDefinitionInputs
SchemaSchemaCapability2isViewFunction, getWritableFunctions
TypeMappingTypeMappingCapability2mapParameterTypeToFieldType, getTypeMappingInfo
QueryQueryCapability2queryViewFunction, formatFunctionResult, getCurrentBlock
ExecutionExecutionCapability3signAndBroadcast, formatTransactionData, validateExecutionConfig
WalletWalletCapability3connectWallet, disconnectWallet, getWalletConnectionStatus
UiKitUiKitCapability3getAvailableUiKits, configureUiKit
RelayerRelayerCapability3getRelayers, getNetworkServiceForms
AccessControlAccessControlCapability3registerContract, 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

CapabilityDeclarativeViewerTransactorComposerOperator
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 explorersDeclarative
Read contract state without sending transactionsViewer
Send transactions without reading contract state firstTransactor
Build full contract interaction UIs with relayer supportComposer
Manage contract roles and permissionsOperator

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.

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.

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:

// 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.