From cdd13a30d115899429bce0ad5dd6b58ee3f84e10 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:38:14 +0000 Subject: [PATCH 1/3] Fix OTP verification error handling to properly propagate errors to UI - Modified verifyOtp() to explicitly return Promise and properly throw errors - Modified sendMessageWithOtp() to throw errors instead of only rejecting internal promise - Updated React UI CrossmintWalletProvider to re-throw errors so they reach BaseCodeInput - Errors now properly display to users when OTP verification fails or rate limits occur - Maintains backward compatibility with existing onAuthRequired callback signature Co-Authored-By: jorge@paella.dev --- .../src/providers/CrossmintWalletProvider.tsx | 8 ++++---- .../wallets/src/signers/non-custodial/ncs-signer.ts | 13 ++++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx b/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx index f1f8472b3..1c5eeec29 100644 --- a/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx +++ b/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx @@ -102,7 +102,7 @@ export function CrossmintWalletProvider({ setEmailSignerDialogStep("otp"); } catch (error) { console.error("Failed to send email OTP", error); - rejectRef.current(new Error("Failed to send email OTP")); + throw error; } }; @@ -112,7 +112,7 @@ export function CrossmintWalletProvider({ setEmailSignerDialogOpen(false); } catch (error) { console.error("Failed to verify OTP", error); - rejectRef.current(new Error("Failed to verify OTP")); + throw error; } }; @@ -123,7 +123,7 @@ export function CrossmintWalletProvider({ setPhoneSignerDialogStep("otp"); } catch (error) { console.error("Failed to send phone OTP", error); - rejectRef.current(new Error("Failed to send phone OTP")); + throw error; } }; @@ -133,7 +133,7 @@ export function CrossmintWalletProvider({ setPhoneSignerDialogOpen(false); } catch (error) { console.error("Failed to verify phone OTP", error); - rejectRef.current(new Error("Failed to verify phone OTP")); + throw error; } }; diff --git a/packages/wallets/src/signers/non-custodial/ncs-signer.ts b/packages/wallets/src/signers/non-custodial/ncs-signer.ts index 35f92a3f4..2d632a51f 100644 --- a/packages/wallets/src/signers/non-custodial/ncs-signer.ts +++ b/packages/wallets/src/signers/non-custodial/ncs-signer.ts @@ -180,7 +180,7 @@ export abstract class NonCustodialSigner implements Signer { return { promise, resolve: resolvePromise, reject: rejectPromise }; } - private async sendMessageWithOtp() { + private async sendMessageWithOtp(): Promise { const handshakeParent = await this.getTEEConnection(); const authId = this.getAuthId(); const response = await handshakeParent.sendAction({ @@ -206,7 +206,9 @@ export abstract class NonCustodialSigner implements Signer { if (response?.status === "error") { console.error("[sendMessageWithOtp] Failed to send OTP:", response); - this._authPromise?.reject(new Error(response.error || "Failed to initiate OTP process.")); + const error = new Error(response.error || "Failed to initiate OTP process."); + this._authPromise?.reject(error); + throw error; } } @@ -217,7 +219,7 @@ export abstract class NonCustodialSigner implements Signer { return `phone:${this.config.phone}`; } - private async verifyOtp(encryptedOtp: string) { + private async verifyOtp(encryptedOtp: string): Promise { let response: SignerOutputEvent<"complete-onboarding">; try { const handshakeParent = await this.getTEEConnection(); @@ -241,8 +243,9 @@ export abstract class NonCustodialSigner implements Signer { } catch (err) { console.error("[verifyOtp] Error sending OTP validation request:", err); this._needsAuth = true; - this._authPromise?.reject(err as Error); - throw err; + const error = err as Error; + this._authPromise?.reject(error); + throw error; } if (response?.status === "success") { From 37b1954759cdf1fe0d697b31d3bea8872f3ff593 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:49:02 +0000 Subject: [PATCH 2/3] Add changeset for OTP error handling fix Co-Authored-By: jorge@paella.dev --- .changeset/fix-otp-error-handling.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/fix-otp-error-handling.md diff --git a/.changeset/fix-otp-error-handling.md b/.changeset/fix-otp-error-handling.md new file mode 100644 index 000000000..b00f217ef --- /dev/null +++ b/.changeset/fix-otp-error-handling.md @@ -0,0 +1,6 @@ +--- +"@crossmint/client-sdk-wallets": patch +"@crossmint/client-sdk-react-ui": patch +--- + +Fix OTP verification error handling to properly propagate errors to UI. When users entered an incorrect OTP or hit rate limits, errors are now properly displayed instead of silently restarting the auth flow. From 4f2f73ebdad430c7a1352fa549e8dfd8a5617c44 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:19:11 +0000 Subject: [PATCH 3/3] Address PR feedback: remove explicit return types and unnecessary variable Co-Authored-By: jorge@paella.dev --- packages/wallets/src/signers/non-custodial/ncs-signer.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/wallets/src/signers/non-custodial/ncs-signer.ts b/packages/wallets/src/signers/non-custodial/ncs-signer.ts index 2d632a51f..51d390662 100644 --- a/packages/wallets/src/signers/non-custodial/ncs-signer.ts +++ b/packages/wallets/src/signers/non-custodial/ncs-signer.ts @@ -180,7 +180,7 @@ export abstract class NonCustodialSigner implements Signer { return { promise, resolve: resolvePromise, reject: rejectPromise }; } - private async sendMessageWithOtp(): Promise { + private async sendMessageWithOtp() { const handshakeParent = await this.getTEEConnection(); const authId = this.getAuthId(); const response = await handshakeParent.sendAction({ @@ -219,7 +219,7 @@ export abstract class NonCustodialSigner implements Signer { return `phone:${this.config.phone}`; } - private async verifyOtp(encryptedOtp: string): Promise { + private async verifyOtp(encryptedOtp: string) { let response: SignerOutputEvent<"complete-onboarding">; try { const handshakeParent = await this.getTEEConnection(); @@ -243,9 +243,8 @@ export abstract class NonCustodialSigner implements Signer { } catch (err) { console.error("[verifyOtp] Error sending OTP validation request:", err); this._needsAuth = true; - const error = err as Error; - this._authPromise?.reject(error); - throw error; + this._authPromise?.reject(err as Error); + throw err; } if (response?.status === "success") {