Skip to content

feat: unify OTP signer API with showOtpSignerPrompt and otpSignerState#1699

Merged
albertoelias-crossmint merged 3 commits intowallets-v1from
devin/1774025874-unify-otp-signer-api
Mar 20, 2026
Merged

feat: unify OTP signer API with showOtpSignerPrompt and otpSignerState#1699
albertoelias-crossmint merged 3 commits intowallets-v1from
devin/1774025874-unify-otp-signer-api

Conversation

@albertoelias-crossmint
Copy link
Collaborator

@albertoelias-crossmint albertoelias-crossmint commented Mar 20, 2026

Description

Unifies the OTP signing API across react-ui and react-native SDKs by:

  1. Replacing headlessSigningFlow with showOtpSignerPrompt — semantically inverted: true (default) means "show built-in OTP UI", false means "handle manually via useWalletOtpSigner hook". This prop is now available in both react-ui and react-native (previously react-native only).

  2. Renaming emailSignerStateotpSignerState — reflects that the state handles both email and phone OTP signers, not just email.

  3. useWalletOtpSigner already works in both SDKs (exports were in place); this PR ensures the underlying context property it reads from is correctly renamed.

  4. OTP gating is scoped to OTP dialogs onlyshowOtpSignerPrompt={false} suppresses Email/Phone signer dialogs but does not suppress passkey helper UI. This is achieved by moving the gating logic into the platform-specific render functions (createRenderWebUI in react-ui, renderNativeUI in react-native) rather than nulling out renderUI entirely.

⚠️ Key review points

  • React Native default behavior change: Previously headlessSigningFlow defaulted to true (no UI rendered). Now showOtpSignerPrompt defaults to true (UI rendered). This inverts the default for React Native consumers. Intentional for wallets-v1 API unification, but worth confirming.
  • renderUI is now always passed: Both react-ui and react-native always provide a renderUI function to CrossmintWalletBaseProvider. The OTP/passkey separation happens inside the render functions themselves. Verify this doesn't cause issues if downstream code assumed renderUI could be undefined when OTP UI was disabled.
  • createRenderWebUI creates a new function reference each render (react-ui, line 69). This is a minor perf concern — could be wrapped in useMemo if re-render frequency becomes an issue. Worth a quick check that CrossmintWalletUIBaseProvider doesn't do deep equality checks on renderUI.
  • showOtpSignerPrompt is still accepted by CrossmintWalletUIBaseProvider but is no longer used there (gating moved to platform render functions). Could be cleaned up in a follow-up.
  • No end-to-end OTP flow testing: Changes verified via lint, build, and unit tests only. Manual testing of an actual OTP signing flow would increase confidence.

Human review checklist

  • Confirm the RN default behavior inversion (headless→UI-shown) is intentional for wallets-v1
  • Verify no downstream code assumes renderUI is undefined when OTP is disabled
  • Check if createRenderWebUI recreating on each render causes any perf issues

Test plan

  • Ran pnpm lint — passes with no errors
  • Ran pnpm test:vitest — all 11 test suites pass (298+ tests)
  • Ran pnpm build:libs — all 17 packages build successfully
  • Verified via grep that zero references to emailSignerState or headlessSigningFlow remain in .ts/.tsx files
  • Verified useWalletOtpSigner is exported from both react-ui and react-native hooks/index.ts

Package updates

Updated changeset (use-wallet-otp-signer.md) covering minor bumps for:

  • @crossmint/wallets-sdk
  • @crossmint/client-sdk-react-ui
  • @crossmint/client-sdk-react-native-ui
  • @crossmint/client-sdk-react-base

Link to Devin session: https://crossmint.devinenterprise.com/sessions/98bcfbc9efce43389139305a3f7b3c5f
Requested by: @albertoelias-crossmint


Open with Devin

- Replace headlessSigningFlow with showOtpSignerPrompt (defaults true)
- Add showOtpSignerPrompt to react-ui CrossmintWalletProvider
- Rename emailSignerState to otpSignerState in context
- Update useWalletOtpSigner to use otpSignerState
- Update demo app and README references

Co-Authored-By: Alberto Elias <alberto.elias@paella.dev>
@devin-ai-integration
Copy link
Contributor

Original prompt from Alberto Elias

Between react native and react-ui the way to sign transactions with the otp flow is different and inconsistent between each other. we have the headlessSigning field, useWalletOtpSigner hook etc. Can you explain the differences?

You only need to look in the following repo: Crossmint/crossmint-sdk

@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@changeset-bot
Copy link

changeset-bot bot commented Mar 20, 2026

⚠️ No Changeset found

Latest commit: 5ef83df

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 20, 2026

Prompt To Fix All With AI
This is a comment left during a code review.
Path: .changeset/use-wallet-otp-signer.md
Line: 3

Comment:
**Breaking default-behavior change marked as `minor`**

`@crossmint/client-sdk-react-native-ui` is marked as a `minor` bump, but the change from `headlessSigningFlow=true` (default: headless, no UI) to `showOtpSignerPrompt=true` (default: shows UI) is a **breaking change** for existing react-native consumers.

Any consumer who did **not** previously set `headlessSigningFlow` was relying on the default headless behavior. After this upgrade, they will suddenly see OTP UI dialogs during signing flows without changing any code. Under semver, a change in default behavior that can break existing consumers warrants a `major` version bump.

```suggestion
"@crossmint/client-sdk-react-native-ui": major
```

At minimum, the migration guide / changeset description should prominently call out that react-native consumers relying on the headless default must now explicitly pass `showOtpSignerPrompt={false}` to preserve their previous behavior.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx
Line: 69

Comment:
**Disabling OTP prompt also silently disables passkey helper UI**

When `showOtpSignerPrompt` is `false`, `renderUI` is set to `undefined` on this line, so `renderWebUI` is never invoked. Because `renderWebUI` renders **both** the OTP dialogs **and** `PasskeyPrompt`, opting out of built-in OTP handling inadvertently suppresses the passkey helper UI even when `showPasskeyHelpers` is `true`.

Before this PR, `renderUI` was unconditionally set to `renderWebUI` in react-ui (headless OTP was not supported), so the passkey prompt was always rendered. A developer who sets `showOtpSignerPrompt` to `false` (to handle OTP manually via the hook) while expecting passkey helpers to remain active will silently lose that UI.

A cleaner fix would be to always pass `renderUI={renderWebUI}` and let `renderWebUI` (or `CrossmintWalletUIBaseProvider`) conditionally skip the email/phone dialogs based on `showOtpSignerPrompt`, while always rendering `PasskeyPrompt` when passkey helpers are enabled.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "feat: unify OTP sign..."

devin-ai-integration[bot]

This comment was marked as resolved.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 20, 2026

🔥 Smoke Test Results

Status: Passed

Statistics

  • Total Tests: 5
  • Passed: 5 ✅
  • Failed: 0
  • Skipped: 0
  • Duration: 3.15 min

✅ All smoke tests passed!

All critical flows are working correctly.


This is a non-blocking smoke test. Full regression tests run separately.

…s to true

Co-Authored-By: Alberto Elias <alberto.elias@paella.dev>
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 20, 2026

Prompt To Fix All With AI
This is a comment left during a code review.
Path: .changeset/use-wallet-otp-signer.md
Line: 3-5

Comment:
**Breaking default behavior for React Native warrants major bump**

The React Native `CrossmintWalletProvider` inverts its default behavior in this PR: `headlessSigningFlow` previously defaulted to `true` (no UI rendered), while `showOtpSignerPrompt` now defaults to `true` (UI **is** rendered). Existing React Native consumers who omitted `headlessSigningFlow` — relying on the headless default — will now unexpectedly see the built-in OTP dialogs after upgrading.

Under semver, a default-behavior reversal that causes consumers' apps to silently change at runtime is a breaking change. `@crossmint/client-sdk-react-native-ui` (and potentially `@crossmint/client-sdk-react-base`) should be bumped to `major` rather than `minor`.

```suggestion
"@crossmint/wallets-sdk": minor
"@crossmint/client-sdk-react-ui": minor
"@crossmint/client-sdk-react-native-ui": major
"@crossmint/client-sdk-react-base": major
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "fix: remove explicit..."

Move OTP dialog gating into the renderUI functions themselves so that
passkey prompts are always rendered regardless of showOtpSignerPrompt.
renderUI is now always passed to CrossmintWalletBaseProvider.

Co-Authored-By: Alberto Elias <alberto.elias@paella.dev>
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 20, 2026

Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/client/react-base/src/providers/CrossmintWalletUIBaseProvider.tsx
Line: 12-13

Comment:
**Unused prop `showOtpSignerPrompt` in interface and component**

`showOtpSignerPrompt` is declared in `CrossmintWalletUIBaseProviderProps` (line 12) and destructured in the component (line 53), but it's never referenced in the component body. The conditional rendering logic was moved into the `renderUI` callbacks in both react-ui (`createRenderWebUI`) and react-native (`renderNativeUI`), making this prop dead code in the base provider.

Consider removing it from the interface and destructuring to keep the code clean.

```suggestion
    showPasskeyHelpers?: boolean;
```

**Rule Used:** Remove unused code from PRs to keep them lean. Add... ([source](https://app.greptile.com/review/custom-context?memory=83771aea-3c5f-4a37-9170-8ac881cd5efd))

**Learnt From**
[Paella-Labs/crossbit-main#20960](https://github.com/Paella-Labs/crossbit-main/pull/20960)

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx
Line: 69

Comment:
**New function created on every render**

`createRenderWebUI(showOtpSignerPrompt)` is called inline on every render of `CrossmintWalletProvider`, producing a fresh function reference each time. Since this is a provider that sits high in the component tree, consider memoizing the result to maintain referential stability:

```
const renderUI = useMemo(() => createRenderWebUI(showOtpSignerPrompt), [showOtpSignerPrompt]);
```

Then pass `renderUI={renderUI}` to `CrossmintWalletBaseProvider`.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "fix: showOtpSignerPr..."

@albertoelias-crossmint albertoelias-crossmint merged commit d8fe0eb into wallets-v1 Mar 20, 2026
3 checks passed
@albertoelias-crossmint albertoelias-crossmint deleted the devin/1774025874-unify-otp-signer-api branch March 20, 2026 23:13
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.

2 participants