Skip to content

feat(adapter-evm-core): add hasIdentityKeyPurpose IRS read for saga resume - #66

Merged
pasevin merged 1 commit into
mainfrom
feat/irs-has-identity-key-purpose
Jul 27, 2026
Merged

feat(adapter-evm-core): add hasIdentityKeyPurpose IRS read for saga resume#66
pasevin merged 1 commit into
mainfrom
feat/irs-has-identity-key-purpose

Conversation

@pasevin

@pasevin pasevin commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a declared, saga-safe IRS key-purpose read: hasIdentityKeyPurpose.

hasIdentityKeyPurpose(input: {
  onchainId: string;
  address: string;
  purpose: number;
}): Promise<IdentityKeyPurposeLookup>

type IdentityKeyPurposeLookup =
  | { readonly status: 'has' }
  | { readonly status: 'lacks' }
  | { readonly status: 'read_failed'; readonly cause: Error }

What

Before this change, the only key-purpose probe was internal identityKeyHasPurpose, which awaits readContract with no try/catch. A transport failure propagates as a raw throw. There was no declared read a consumer can safely branch on when resuming a partially completed onboard.

This PR adds lookupIdentityKeyPurpose (reader) and exposes hasIdentityKeyPurpose on EvmIRSCapability, matching the getFactoryIdentity convention (found / not_found / read_failed → here has / lacks / read_failed).

Why it matters

A consumer wrapping the internal probe naively (catch → treat as lacks) will re-attempt grantHolderManagementKey. addKey reverts on duplicate purpose — that is the orphan trap.

This week, that exact defect family — an RPC error collapsed into a negative answer — stranded three Sepolia identities that could not be re-onboarded. The three-way union is not over-engineering; it is the minimum surface that prevents a fourth address landing in the same state.

read_failed is a value, never an exception and never lacks. Tests assert this explicitly:

expect(lookup.status).toBe('read_failed');
expect(lookup).not.toEqual({ status: 'lacks' });

Ordering with openzeppelin-ui PR #202

openzeppelin-ui PR #202 declares hasIdentityKeyPurpose on IRSCapability in @openzeppelin/ui-types ahead of this implementation publishing. A reviewer seeing a declared-but-not-yet-shipped method should read that as intentional contract-first sequencing, not drift.

Release coordination (important): Once #202 publishes, IRSCapability will require hasIdentityKeyPurpose while published @openzeppelin/adapter-evm 2.4.0 does not implement it. Consumers on a caret ui-types range could type-check cleanly against a method the installed runtime lacks and crash at call time. The adapter itself avoids a compile break in that window via as unknown as EvmIRSCapability at capabilities/irs.ts:106 — that cast papers over the gap for this repo's build, not for downstream apps. This adapters release should land before or with the ui-types publish, not after.

Red-first caveat (honest)

The new tests fail on origin/main because the API is additive (6/6 TypeError: … is not a function) — a weaker RED than a behavioural failure on pre-existing code. The behavioural guarantee is pinned by the explicit not.toEqual({ status: 'lacks' }) assertion on RPC failure, not by catching a pre-existing wrong return value. Evidence: scratchpad/key-purpose-evidence.md (internal).

Semver

Minor (@openzeppelin/adapter-evm) — additive capability read consumed by SC009 saga resume.

Test plan

  • pnpm --filter @openzeppelin/adapter-evm-core typecheck lint test build
  • pnpm --filter @openzeppelin/adapter-evm typecheck test
  • RED-first: 6/6 new tests fail on origin/main src (zero false greens); 6/6 pass with implementation

Made with Cursor

…esume

Expose a declared key-purpose probe matching getFactoryIdentity semantics:
has / lacks / read_failed (transport failures never collapse into lacks).

Implements the IRSCapability method declared ahead of publication in
openzeppelin-ui PR #202. Merge ui-types #202 before this adapters release.

Co-authored-by: Cursor <cursoragent@cursor.com>
@pasevin
pasevin merged commit 465b865 into main Jul 27, 2026
9 checks passed
@pasevin
pasevin deleted the feat/irs-has-identity-key-purpose branch July 27, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant