From fe6251bfe10bb9b0071c72cd0963372a544e3fe5 Mon Sep 17 00:00:00 2001 From: Aleksandr Pasevin Date: Mon, 27 Jul 2026 15:15:04 +0100 Subject: [PATCH] feat(types): extend IRSCapability with factory lookup, key probe, and management grant Co-authored-by: Cursor --- .changeset/irs-capability-extended-surface.md | 9 +++++ .../__tests__/ri-capabilities.types.test.ts | 11 ++++++ .../types/src/adapters/capabilities/irs.ts | 36 +++++++++++++++++++ packages/types/src/adapters/irs.ts | 20 +++++++++++ 4 files changed, 76 insertions(+) create mode 100644 .changeset/irs-capability-extended-surface.md diff --git a/.changeset/irs-capability-extended-surface.md b/.changeset/irs-capability-extended-surface.md new file mode 100644 index 00000000..34cc7265 --- /dev/null +++ b/.changeset/irs-capability-extended-surface.md @@ -0,0 +1,9 @@ +--- +"@openzeppelin/ui-types": minor +--- + +Extend `IRSCapability` with factory identity lookup, key-purpose probe, and holder management-key grant. + +Adds `getFactoryIdentity`, `hasIdentityKeyPurpose`, and `grantHolderManagementKey` with discriminated read results (`read_failed` distinct from negative answers). Aligns the shared capability contract with adapter-evm 2.4.0+ so consumers no longer need structural casts at the IRS boundary. + +**Breaking for implementors:** any external `IRSCapability` implementor must now provide all three members or fail to compile; consumers calling the capability are unaffected. No in-repo or in-adapters implementor is impacted today (only adapter-evm implements IRS, and it already ships these methods), so practical blast radius is limited to out-of-tree implementations. diff --git a/packages/types/src/adapters/__tests__/ri-capabilities.types.test.ts b/packages/types/src/adapters/__tests__/ri-capabilities.types.test.ts index 6ce920df..eb69f461 100644 --- a/packages/types/src/adapters/__tests__/ri-capabilities.types.test.ts +++ b/packages/types/src/adapters/__tests__/ri-capabilities.types.test.ts @@ -16,6 +16,8 @@ import type { ClaimPayload, ERC3643Capability, ERC4626Capability, + FactoryIdentityLookup, + IdentityKeyPurposeLookup, IdentityRegistration, IRSCapability, NetworkConfig, @@ -69,6 +71,14 @@ const irsStub = { networkConfig, dispose: () => undefined, getOnchainId: async (_holder: string): Promise => ({ found: false }), + getFactoryIdentity: async (_holder: string): Promise => ({ + status: 'not_found', + }), + hasIdentityKeyPurpose: async (_input: { + onchainId: string; + address: string; + purpose: number; + }): Promise => ({ status: 'lacks' }), isVerified: async (_holder: string): Promise => false, getJurisdiction: async (_holder: string): Promise => undefined, buildClaimPayload: (input: { @@ -83,6 +93,7 @@ const irsStub = { data: input.data, }), deployOnchainId: async (_input: { holder: string }) => ({ ...opResult, onchainId: '0x' }), + grantHolderManagementKey: async (_input: { onchainId: string; holder: string }) => opResult, registerTrustedIssuer: async (_input: { issuer: string; topics: string[] }) => opResult, attachClaim: async (_input: { onchainId: string; claim: OnboardingClaim }) => opResult, registerIdentity: async (_input: IdentityRegistration) => opResult, diff --git a/packages/types/src/adapters/capabilities/irs.ts b/packages/types/src/adapters/capabilities/irs.ts index 6226f552..5fee5f67 100644 --- a/packages/types/src/adapters/capabilities/irs.ts +++ b/packages/types/src/adapters/capabilities/irs.ts @@ -4,6 +4,8 @@ import type { OperationResult } from '../access-control'; import type { ClaimPayload, DeployOnchainIdResult, + FactoryIdentityLookup, + IdentityKeyPurposeLookup, IdentityRegistration, OnboardingClaim, OnchainIdLookup, @@ -35,6 +37,27 @@ export interface IRSCapability extends RuntimeCapability { */ getOnchainId(holder: string): Promise; + /** + * Resolve the ONCHAINID the identity factory deployed for `holder`. + * + * Used by resume/idempotency paths that must detect deployed-but-unregistered holders. + * `not_found` is distinct from `read_failed` — transport failures must not be treated as + * "no identity". + */ + getFactoryIdentity(holder: string): Promise; + + /** + * Probe whether `address` holds `purpose` on an ONCHAINID identity. + * + * Used by resume/idempotency paths that must detect whether `grantHolderManagementKey` + * already ran — `read_failed` must not be treated as `lacks`. + */ + hasIdentityKeyPurpose(input: { + onchainId: string; + address: string; + purpose: number; + }): Promise; + /** * The IRS verification pre-check. * Returns `false` (never throws) when `holder` is unregistered. @@ -75,6 +98,19 @@ export interface IRSCapability extends RuntimeCapability { runtimeApiKey?: string ): Promise; + /** + * Grant the holder a MANAGEMENT key on their ONCHAINID (submits `addKey(holder, MANAGEMENT)`). + * + * **Saga ordering is load-bearing:** consumers MUST call this after `deployOnchainId` and + * **before** `attachClaim` so the holder can rescue their identity if a later step fails. + */ + grantHolderManagementKey( + input: { onchainId: string; holder: string }, + executionConfig: ExecutionConfig, + onStatusChange?: (status: TxStatus, details: TransactionStatusUpdate) => void, + runtimeApiKey?: string + ): Promise; + /** * Register a trusted issuer for the given claim `topics`. Idempotent: safe to * re-run when the issuer is already registered. diff --git a/packages/types/src/adapters/irs.ts b/packages/types/src/adapters/irs.ts index 26087a26..40e397fd 100644 --- a/packages/types/src/adapters/irs.ts +++ b/packages/types/src/adapters/irs.ts @@ -72,6 +72,26 @@ export interface OnchainIdLookup { onchainId?: string; } +/** + * Result of resolving the ONCHAINID deployed for a wallet via the identity factory. + * + * `not_found` (zero address) is distinct from `read_failed` (RPC/transport failure). + */ +export type FactoryIdentityLookup = + | { readonly status: 'found'; readonly onchainId: string } + | { readonly status: 'not_found' } + | { readonly status: 'read_failed'; readonly cause: Error }; + +/** + * Result of probing whether an address holds a given ERC-734 key purpose on an ONCHAINID. + * + * `lacks` (on-chain false) is distinct from `read_failed` (RPC/transport failure). + */ +export type IdentityKeyPurposeLookup = + | { readonly status: 'has' } + | { readonly status: 'lacks' } + | { readonly status: 'read_failed'; readonly cause: Error }; + /** * Result of `deployOnchainId`: the operation id plus the freshly deployed ONCHAINID address. */