Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/irs-capability-extended-surface.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import type {
ClaimPayload,
ERC3643Capability,
ERC4626Capability,
FactoryIdentityLookup,
IdentityKeyPurposeLookup,
IdentityRegistration,
IRSCapability,
NetworkConfig,
Expand Down Expand Up @@ -69,6 +71,14 @@ const irsStub = {
networkConfig,
dispose: () => undefined,
getOnchainId: async (_holder: string): Promise<OnchainIdLookup> => ({ found: false }),
getFactoryIdentity: async (_holder: string): Promise<FactoryIdentityLookup> => ({
status: 'not_found',
}),
hasIdentityKeyPurpose: async (_input: {
onchainId: string;
address: string;
purpose: number;
}): Promise<IdentityKeyPurposeLookup> => ({ status: 'lacks' }),
isVerified: async (_holder: string): Promise<boolean> => false,
getJurisdiction: async (_holder: string): Promise<string | undefined> => undefined,
buildClaimPayload: (input: {
Expand All @@ -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,
Expand Down
36 changes: 36 additions & 0 deletions packages/types/src/adapters/capabilities/irs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { OperationResult } from '../access-control';
import type {
ClaimPayload,
DeployOnchainIdResult,
FactoryIdentityLookup,
IdentityKeyPurposeLookup,
IdentityRegistration,
OnboardingClaim,
OnchainIdLookup,
Expand Down Expand Up @@ -35,6 +37,27 @@ export interface IRSCapability extends RuntimeCapability {
*/
getOnchainId(holder: string): Promise<OnchainIdLookup>;

/**
* 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<FactoryIdentityLookup>;

/**
* 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<IdentityKeyPurposeLookup>;

/**
* The IRS verification pre-check.
* Returns `false` (never throws) when `holder` is unregistered.
Expand Down Expand Up @@ -75,6 +98,19 @@ export interface IRSCapability extends RuntimeCapability {
runtimeApiKey?: string
): Promise<DeployOnchainIdResult>;

/**
* 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<OperationResult>;

/**
* Register a trusted issuer for the given claim `topics`. Idempotent: safe to
* re-run when the issuer is already registered.
Expand Down
20 changes: 20 additions & 0 deletions packages/types/src/adapters/irs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Loading