Skip to content

Commit 180826c

Browse files
authored
feat!: migrate Role Manager to operator profile runtime (#96)
* feat(role-manager): adopt capability-adapters runtime architecture Migrate from monolithic ContractAdapter to profile-based EcosystemRuntime: - Replace AdapterProvider with RuntimeProvider, getAdapter with getRuntime - Introduce RoleManagerRuntime (OperatorEcosystemRuntime + relayer) and RoleManagerAdapter compatibility shim for incremental hook migration - Add legacyOperatorRuntime bridge for adapters still on createAdapter - Migrate WalletSyncProvider to activeRuntime API with ecosystem-scoped wallet session support and simplified chain switch logic - Update all hooks/tests from ContractAdapter to RoleManagerAdapter types - Remove broken contract-adapter-compat.d.ts * chore(role-manager): temporary legacy support * feat(role-manager): remove legacy operator runtime and map adapter-evm-core locally Require createRuntime for operator profile. Extend pnpmfile for adapter-evm-core local dev. * feat(role-manager): add runtime disposal lifecycle to useNetworkAdapter Apply the same promote-then-dispose handoff pattern from the shared RuntimeProvider/WalletStateProvider layer to the app-local runtime path. Superseded runtimes are disposed after their replacement is promoted to state, cancelled loads are disposed immediately, and the active runtime is disposed on unmount. Disposal is deferred via setTimeout to avoid RuntimeDisposedError in React's dev-mode commit phase. Updates tests to assert correct disposal behavior. * refactor(role-manager): remove RoleManagerAdapter compatibility layer Migrate all production code and tests from the flattened RoleManagerAdapter interface to capability-qualified RoleManagerRuntime access (runtime.addressing, runtime.explorer, runtime.accessControl, runtime.query, runtime.contractLoading, runtime.relayer). Remove toRoleManagerAdapter(), getAdapter(), getAccessControlService(), and the dual-path fallback in useAccessControlService / useContractRegistration. * chore(deps): regenerate lockfile without local dev tarball paths * chore(deps): update @OpenZeppelin/ui-* to latest published versions * feat(deps): bump @OpenZeppelin/ui-* deps to ^2.0.0 * fix(role-manager): suppress exhaustive-deps for intentional ID-only tracking * feat(ci): add RC adapter resolution for staging builds (#97) Port the RC adapter resolution mechanism from ui-builder so staging Docker builds can consume pre-release adapter packages. - Add scripts/resolve-staging-adapters.cjs that queries npm for "rc" and "latest" dist-tags, picks the newer version, and runs a surgical pnpm add --save-exact for adapter packages only. - Update Dockerfile with ADAPTER_DIST_TAG build arg and resolution step between frozen-lockfile install and build. - Pass ADAPTER_DIST_TAG=rc in the staging workflow build-args. * feat(deps): bump adapter packages to ^2.0.0 with pre-release resolution Bump all @openzeppelin/adapter-* and adapters-vite dependencies to ^2.0.0 for the capability adapters migration. Extend .pnpmfile.cjs to widen adapter caret ranges to include pre-release versions during resolution, so pnpm can resolve to RC packages when the stable version hasn't shipped yet. Once stable 2.0.0 is published, pnpm naturally resolves to it instead. * fix(role-manager): align test mocks and component with v2 runtime API - WalletSyncProvider: read activeRuntime/isRuntimeLoading, pass wallet + networkCatalog as separate props to NetworkSwitchManager, skip chain switch on initial sync and cross-ecosystem switches - Nest getExplorerUrl/getExplorerTxUrl under runtime.explorer in mocks - Update error assertions from "runtime" to "adapter" wording - Fix fake timer cascading timeouts in useNetworkAdapter tests * fix(role-manager): address PR review feedback - Use Object.create instead of spread in attachRelayerCapability to preserve runtime proxy behavior and object identity - Use execFileSync in resolve-staging-adapters to prevent shell injection - Include @openzeppelin/adapters-vite in prerelease version widening - Update stale adapter terminology in comments, variables, JSDoc, and user-facing copy to use runtime language * fix(role-manager): update lockfile pnpmfileChecksum The previous commit modified .pnpmfile.cjs without updating the lockfile checksum, causing CI to fail with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH on --frozen-lockfile. * fix(role-manager): address second round of PR review feedback - Use execFileSync for pnpm add in resolve-staging-adapters to eliminate remaining shell injection vector; remove unused execSync import - Dispose runtime immediately after extracting addressing capability in AddressBook.resolveAddressing to prevent resource leaks - Dispose previous promoted runtime on getRuntime error path in useNetworkAdapter so stale connections don't persist - Update Dockerfile comment to reflect that .pnpmfile.cjs now also runs the prerelease-widening hook (not purely a no-op in Docker) * fix(role-manager): update stale adapter terminology in JSDoc and comments - useContractSchema: @param now says "runtime" instead of "adapter" - useExpirationMetadata: file-level JSDoc updated to reference runtime's accessControl capability instead of "adapter.getExpirationMetadata()" - AddContractDialog: inline comment updated to "Runtime for schema loading" * fix(role-manager): harden parseSemver in resolve-staging-adapters Strip build metadata (+...) before parsing and validate that major/minor/patch are finite numbers to avoid silent NaN comparisons. * fix(role-manager): update stale adapter terminology and harden pickBestVersion - useExpirationMetadata guard comment now references runtime/accessControl - App.tsx provider-hierarchy comment updated to "runtime session" - pickBestVersion only prefers latest over rc when latest is a stable (non-prerelease) release, preventing incorrect downgrades * fix(role-manager): correct caret upper bound for 0.x ranges in pnpmfile allowAdapterPrereleases now computes the caret ceiling correctly for major-zero ranges (^0.3.0 -> <0.4.0 instead of <1.0.0), matching npm/pnpm caret semantics and preventing unintended major upgrades.
1 parent 6f4261d commit 180826c

File tree

85 files changed

+2272
-1535
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+2272
-1535
lines changed

.github/workflows/docker-stg.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ jobs:
214214
build-args: |
215215
VITE_APP_CFG_FEATURE_FLAG_ANALYTICS_ENABLED=true
216216
VITE_GA_TAG_ID=${{ secrets.VITE_GA_TAG_ID_STAGING }}
217+
ADAPTER_DIST_TAG=rc
217218
secrets: |
218219
etherscan_api_key=${{ secrets.VITE_APP_CFG_SERVICE_ETHERSCANV2_API_KEY }}
219220
routescan_api_key=${{ secrets.VITE_APP_CFG_SERVICE_ROUTESCAN_API_KEY }}

.pnpmfile.cjs

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const STANDARD_FAMILIES = {
3333
defaultPath: '../openzeppelin-adapters',
3434
packageMap: {
3535
'@openzeppelin/adapters-vite': 'packages/adapters-vite',
36+
'@openzeppelin/adapter-runtime-utils': 'packages/adapter-runtime-utils',
37+
'@openzeppelin/adapter-evm-core': 'packages/adapter-evm-core',
3638
'@openzeppelin/adapter-evm': 'packages/adapter-evm',
3739
'@openzeppelin/adapter-midnight': 'packages/adapter-midnight',
3840
'@openzeppelin/adapter-polkadot': 'packages/adapter-polkadot',
@@ -210,22 +212,53 @@ function rewriteDependencies(pkg, context, cacheDir, familyKey, family) {
210212
}
211213
}
212214

213-
function readPackage(pkg, context) {
214-
if (!isAnyLocalFamilyEnabled()) {
215-
return pkg;
215+
/**
216+
* Widen `^X.Y.Z` ranges on `@openzeppelin/adapter*` packages to also include
217+
* pre-release versions (`>=X.Y.Z-0 <(X+1).0.0`).
218+
*
219+
* This lets `pnpm install` resolve RC packages (e.g. 2.0.0-rc.1) when the
220+
* stable version (e.g. 2.0.0) hasn't been published yet, without changing
221+
* the declared range in package.json. Once the stable version ships, pnpm
222+
* naturally resolves to it (highest match wins). The rewrite is harmless
223+
* when stable versions are available — it's effectively a permanent no-op.
224+
*
225+
* Skips deps already rewritten to `file:` paths by local-dev mode.
226+
*/
227+
function allowAdapterPrereleases(pkg) {
228+
for (const depType of ['dependencies', 'devDependencies']) {
229+
if (!pkg[depType]) continue;
230+
for (const [name, range] of Object.entries(pkg[depType])) {
231+
if (!name.startsWith('@openzeppelin/adapter') && !name.startsWith('@openzeppelin/adapters-'))
232+
continue;
233+
const m = range.match(/^\^(\d+)\.(\d+)\.(\d+)$/);
234+
if (!m) continue;
235+
const maj = Number(m[1]), min = Number(m[2]), pat = Number(m[3]);
236+
const upper = maj > 0
237+
? `${maj + 1}.0.0`
238+
: min > 0
239+
? `0.${min + 1}.0`
240+
: `0.0.${pat + 1}`;
241+
pkg[depType][name] = `>=${maj}.${min}.${pat}-0 <${upper}`;
242+
}
216243
}
244+
}
217245

218-
const workspaceRoot = __dirname;
219-
const projectConfig = readProjectConfig(workspaceRoot);
246+
function readPackage(pkg, context) {
247+
if (isAnyLocalFamilyEnabled()) {
248+
const workspaceRoot = __dirname;
249+
const projectConfig = readProjectConfig(workspaceRoot);
220250

221-
for (const [familyKey, family] of Object.entries(projectConfig.families)) {
222-
if (process.env[family.envFlag] !== 'true') {
223-
continue;
224-
}
251+
for (const [familyKey, family] of Object.entries(projectConfig.families)) {
252+
if (process.env[family.envFlag] !== 'true') {
253+
continue;
254+
}
225255

226-
rewriteDependencies(pkg, context, projectConfig.cacheDir, familyKey, family);
256+
rewriteDependencies(pkg, context, projectConfig.cacheDir, familyKey, family);
257+
}
227258
}
228259

260+
allowAdapterPrereleases(pkg);
261+
229262
return pkg;
230263
}
231264

Dockerfile

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,32 @@ RUN rm -rf /root/.cache/node-gyp /root/.npm /root/.node-gyp || true
2828
RUN npm install -g pnpm
2929

3030
# Copy workspace configuration files
31-
# Note: .pnpmfile.cjs is included because pnpm-lock.yaml has a checksum for it
32-
# The hook is a no-op in Docker (LOCAL_UI env var is not set)
31+
# Note: .pnpmfile.cjs is included because pnpm-lock.yaml has a checksum for it.
32+
# The local-dev rewrite hook is a no-op in Docker (LOCAL_* env vars are not set),
33+
# but the prerelease-widening hook (allowAdapterPrereleases) always runs.
3334
COPY ./package.json ./pnpm-lock.yaml ./pnpm-workspace.yaml ./.npmrc ./.pnpmfile.cjs ./
3435
COPY ./tsconfig.json ./tsconfig.base.json ./tsconfig.node.json ./
3536

36-
# Copy all workspace packages and apps
37+
# Copy all workspace packages, apps, and scripts
3738
COPY ./packages ./packages/
3839
COPY ./apps ./apps/
40+
COPY ./scripts ./scripts/
3941

40-
# Install dependencies directly from the public npm registry
42+
# Step 1: Install dependencies from frozen lockfile (exact production dependency tree)
4143
# Retry once on failure after clearing possible corrupted caches to avoid node-gyp issues
4244
RUN pnpm install --frozen-lockfile || (echo "Install failed, clearing caches and retrying..." && rm -rf /root/.cache/node-gyp /root/.npm /root/.node-gyp && pnpm install --frozen-lockfile)
4345

46+
# Step 2: Surgically override adapter packages for staging builds.
47+
# When ADAPTER_DIST_TAG is set (e.g. "rc"), the resolution script queries npm for
48+
# both the requested dist-tag and "latest", picks the newer version per adapter, and
49+
# runs `pnpm add --save-exact` for only those packages. All non-adapter dependencies
50+
# remain byte-for-byte identical to the frozen lockfile from Step 1.
51+
# When ADAPTER_DIST_TAG is unset (production), this step is a no-op.
52+
ARG ADAPTER_DIST_TAG
53+
RUN if [ -n "$ADAPTER_DIST_TAG" ]; then \
54+
node scripts/resolve-staging-adapters.cjs "$ADAPTER_DIST_TAG"; \
55+
fi
56+
4457
# Set Node.js memory limit to prevent OOM during build
4558
# This is especially important for TypeScript compilation in CI environments
4659
ENV NODE_OPTIONS="--max-old-space-size=4096"

apps/role-manager/package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,24 @@
1818
"clean": "rm -rf dist"
1919
},
2020
"dependencies": {
21-
"@openzeppelin/adapter-evm": "^1.1.0",
22-
"@openzeppelin/adapter-polkadot": "^1.1.0",
23-
"@openzeppelin/adapter-stellar": "^1.1.0",
24-
"@openzeppelin/ui-components": "^1.7.0",
25-
"@openzeppelin/ui-react": "^1.2.0",
26-
"@openzeppelin/ui-renderer": "^1.2.0",
21+
"@openzeppelin/adapter-evm": "^2.0.0",
22+
"@openzeppelin/adapter-polkadot": "^2.0.0",
23+
"@openzeppelin/adapter-stellar": "^2.0.0",
24+
"@openzeppelin/ui-components": "^2.0.0",
25+
"@openzeppelin/ui-react": "^2.0.0",
26+
"@openzeppelin/ui-renderer": "^2.0.0",
2727
"@openzeppelin/ui-storage": "^1.2.0",
2828
"@openzeppelin/ui-styles": "^1.0.0",
29-
"@openzeppelin/ui-types": "^1.13.0",
30-
"@openzeppelin/ui-utils": "^1.4.0",
29+
"@openzeppelin/ui-types": "^2.0.0",
30+
"@openzeppelin/ui-utils": "^2.0.0",
3131
"lucide-react": "^0.555.0",
3232
"pluralize": "^8.0.0",
3333
"react": "^19.1.0",
3434
"react-dom": "^19.1.0",
3535
"react-router-dom": "^7.1.1"
3636
},
3737
"devDependencies": {
38-
"@openzeppelin/adapters-vite": "^1.3.2",
38+
"@openzeppelin/adapters-vite": "^2.0.0",
3939
"@tailwindcss/vite": "^4.2.1",
4040
"@types/pluralize": "^0.0.33",
4141
"@types/react": "^19.2.8",

apps/role-manager/src/App.tsx

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
Toaster,
88
TooltipProvider,
99
} from '@openzeppelin/ui-components';
10-
import { AdapterProvider, AnalyticsProvider, WalletStateProvider } from '@openzeppelin/ui-react';
10+
import { AnalyticsProvider, RuntimeProvider, WalletStateProvider } from '@openzeppelin/ui-react';
1111
import type { NativeConfigLoader } from '@openzeppelin/ui-types';
1212

1313
import { TrackedRoute } from './components/Analytics';
@@ -16,7 +16,7 @@ import { AliasLabelBridge } from './context/AliasLabelBridge';
1616
import { BlockTimeProvider } from './context/BlockTimeContext';
1717
import { ContractProvider } from './context/ContractContext';
1818
import { WalletSyncProvider } from './context/WalletSyncProvider';
19-
import { getAdapter, getNetworkById } from './core/ecosystems/ecosystemManager';
19+
import { getNetworkById, getRuntime } from './core/ecosystems/ecosystemManager';
2020
import { AddressBook } from './pages/AddressBook';
2121
import { AuthorizedAccounts } from './pages/AuthorizedAccounts';
2222
import { Dashboard } from './pages/Dashboard';
@@ -62,26 +62,24 @@ function createQueryClient(): QueryClient {
6262
* - BrowserRouter: Client-side routing
6363
* - NetworkErrorNotificationProvider: Error notifications with "Configure" action buttons
6464
* - AnalyticsProvider: Google Analytics tracking (Feature: analytics)
65-
* - AdapterProvider: Manages adapter singleton instances
65+
* - RuntimeProvider: Manages runtime singleton instances
6666
* - ContractProvider: Shared contract selection state (OUTSIDE WalletStateProvider)
6767
* - AliasLabelBridge: Bridges alias storage → AddressLabelProvider (auto-labels all AddressDisplay)
6868
* - WalletStateProvider: Manages wallet connection state
6969
* - WalletSyncProvider: Syncs ContractContext network → WalletStateProvider + handles EVM chain switches
7070
*
7171
* IMPORTANT: ContractProvider must be OUTSIDE WalletStateProvider because
72-
* WalletStateProvider uses a dynamic key for its internal UI context provider.
73-
* When the key changes (adapter loads), React remounts children. If ContractProvider
74-
* were inside, it would remount and reset selectedNetwork to null, causing an
75-
* infinite loop: network selected → adapter loads → provider remounts → network
76-
* resets to null → network selected again...
72+
* WalletStateProvider remounts its internal wallet UI provider when the active
73+
* ecosystem session changes. If ContractProvider were inside, a cross-ecosystem
74+
* switch could remount it and reset selectedNetwork to null, causing a loop.
7775
*
7876
* WalletSyncProvider syncs the selected network from ContractContext to
79-
* WalletStateProvider, enabling the wallet UI to load the correct adapter.
77+
* WalletStateProvider, enabling the wallet UI to load the correct runtime session.
8078
* It also handles EVM chain switching - when users switch between EVM networks,
81-
* it triggers the wallet's chain switch prompt instead of disconnecting.
79+
* it triggers the wallet's chain switch prompt instead of resetting the wallet session.
8280
*
8381
* Feature: 007-dashboard-real-data
84-
* Feature: 013-wallet-connect-header (AdapterProvider, WalletStateProvider, WalletSyncProvider)
82+
* Feature: 013-wallet-connect-header (RuntimeProvider, WalletStateProvider, WalletSyncProvider)
8583
* Feature: network-settings (NetworkErrorNotificationProvider)
8684
*/
8785
function App() {
@@ -124,7 +122,7 @@ function App() {
124122
<NetworkErrorNotificationProvider>
125123
<TooltipProvider delayDuration={300}>
126124
<AnalyticsProvider tagId={analyticsTagId} autoInit>
127-
<AdapterProvider resolveAdapter={getAdapter}>
125+
<RuntimeProvider resolveRuntime={getRuntime}>
128126
<ContractProvider>
129127
<AliasLabelBridge>
130128
<BlockTimeProvider>
@@ -183,7 +181,7 @@ function App() {
183181
</BlockTimeProvider>
184182
</AliasLabelBridge>
185183
</ContractProvider>
186-
</AdapterProvider>
184+
</RuntimeProvider>
187185
</AnalyticsProvider>
188186
<Toaster position="top-right" />
189187
</TooltipProvider>

apps/role-manager/src/components/Admin/TransferAdminDialog.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ export function TransferAdminDialog({
137137
});
138138

139139
// Get adapter for address validation
140-
const { adapter, isAdapterLoading } = useSelectedContract();
140+
const { runtime, isRuntimeLoading } = useSelectedContract();
141141

142142
// React Hook Form setup
143143
const form = useForm<TransferAdminFormData>({
@@ -270,10 +270,10 @@ export function TransferAdminDialog({
270270
default:
271271
return (
272272
<TransferAdminFormContent
273-
key={adapter ? 'with-adapter' : 'no-adapter'}
273+
key={runtime ? 'with-runtime' : 'no-runtime'}
274274
form={form}
275-
adapter={adapter}
276-
isAdapterLoading={isAdapterLoading}
275+
runtime={runtime}
276+
isRuntimeLoading={isRuntimeLoading}
277277
isWalletConnected={isWalletConnected}
278278
requiresExpiration={requiresExpiration}
279279
currentBlock={currentBlock}
@@ -325,8 +325,8 @@ export function TransferAdminDialog({
325325

326326
interface TransferAdminFormContentProps {
327327
form: ReturnType<typeof useForm<TransferAdminFormData>>;
328-
adapter: ReturnType<typeof useSelectedContract>['adapter'];
329-
isAdapterLoading: boolean;
328+
runtime: ReturnType<typeof useSelectedContract>['runtime'];
329+
isRuntimeLoading: boolean;
330330
isWalletConnected: boolean;
331331
/** Whether expiration input is required (adapter says mode: 'required') */
332332
requiresExpiration: boolean;
@@ -341,8 +341,8 @@ interface TransferAdminFormContentProps {
341341

342342
function TransferAdminFormContent({
343343
form,
344-
adapter,
345-
isAdapterLoading,
344+
runtime,
345+
isRuntimeLoading,
346346
isWalletConnected,
347347
requiresExpiration,
348348
currentBlock,
@@ -395,8 +395,8 @@ function TransferAdminFormContent({
395395
const canSubmit =
396396
isValid &&
397397
!isSubmitting &&
398-
!isAdapterLoading &&
399-
adapter !== null &&
398+
!isRuntimeLoading &&
399+
runtime !== null &&
400400
isWalletConnected &&
401401
!expirationError;
402402

@@ -422,13 +422,13 @@ function TransferAdminFormContent({
422422
name="newAdminAddress"
423423
label="New Admin Address"
424424
placeholder={
425-
adapter
426-
? (getEcosystemMetadata(adapter.networkConfig.ecosystem)?.addressExample ?? '0x...')
425+
runtime
426+
? (getEcosystemMetadata(runtime.networkConfig.ecosystem)?.addressExample ?? '0x...')
427427
: '0x...'
428428
}
429429
helperText="The address that will become the new admin of this contract."
430430
control={control}
431-
adapter={adapter ?? undefined}
431+
addressing={runtime?.addressing ?? undefined}
432432
validation={{ required: true }}
433433
/>
434434
</div>
@@ -502,7 +502,7 @@ function TransferAdminFormContent({
502502
Cancel
503503
</Button>
504504
<Button type="submit" disabled={!canSubmit} aria-label="Transfer admin role to new address">
505-
{isAdapterLoading ? 'Loading...' : 'Transfer Admin'}
505+
{isRuntimeLoading ? 'Loading...' : 'Transfer Admin'}
506506
</Button>
507507
</DialogFooter>
508508
</form>

apps/role-manager/src/components/Contracts/AddContractDialog.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,16 @@ export function AddContractDialog({
9797
// Alias storage for auto-creating alias when a contract is added
9898
const { save: saveAlias } = useAliasStorage();
9999

100-
// Network adapter for schema loading (set after form submission)
100+
// Runtime for schema loading (set after form submission)
101101
const [selectedNetwork, setSelectedNetwork] = useState<NetworkConfig | null>(null);
102-
const { adapter, isLoading: isAdapterLoading } = useNetworkAdapter(selectedNetwork);
102+
const { runtime, isLoading: isRuntimeLoading } = useNetworkAdapter(selectedNetwork);
103103

104104
// Schema loader hook
105-
const schemaLoader = useContractSchemaLoader(adapter);
105+
const schemaLoader = useContractSchemaLoader(runtime);
106106

107107
// Access control service for capability detection
108108
const { service: accessControlService, isReady: isAccessControlReady } =
109-
useAccessControlService(adapter);
109+
useAccessControlService(runtime);
110110

111111
/**
112112
* Reset all dialog state to initial values
@@ -153,7 +153,7 @@ export function AddContractDialog({
153153
* Actually load the schema once adapter is ready
154154
*/
155155
const loadSchema = useCallback(async (): Promise<SchemaLoadResult | null> => {
156-
if (!adapter || !pendingFormData) {
156+
if (!runtime || !pendingFormData) {
157157
return null;
158158
}
159159

@@ -162,7 +162,7 @@ export function AddContractDialog({
162162
};
163163

164164
return schemaLoader.load(pendingFormData.address, artifacts);
165-
}, [adapter, pendingFormData, schemaLoader]);
165+
}, [runtime, pendingFormData, schemaLoader]);
166166

167167
/**
168168
* Save contract with schema and capabilities to storage,
@@ -286,14 +286,14 @@ export function AddContractDialog({
286286
useEffect(() => {
287287
if (
288288
step === 'loading-schema' &&
289-
adapter &&
290-
!isAdapterLoading &&
289+
runtime &&
290+
!isRuntimeLoading &&
291291
isAccessControlReady &&
292292
!loadStartedRef.current
293293
) {
294294
executeLoadAndSave();
295295
}
296-
}, [step, adapter, isAdapterLoading, isAccessControlReady, executeLoadAndSave]);
296+
}, [step, runtime, isRuntimeLoading, isAccessControlReady, executeLoadAndSave]);
297297

298298
/**
299299
* Handle retry after error
@@ -324,7 +324,7 @@ export function AddContractDialog({
324324

325325
// Get explorer URL for the contract address
326326
const explorerUrl =
327-
adapter && pendingFormData ? adapter.getExplorerUrl(pendingFormData.address) : null;
327+
runtime && pendingFormData ? runtime.explorer.getExplorerUrl(pendingFormData.address) : null;
328328

329329
return (
330330
<Dialog open={open} onOpenChange={onOpenChange}>

0 commit comments

Comments
 (0)