Skip to content

Integrate Wallet Verification System#139

Merged
MPSxDev merged 1 commit into
PayStell:mainfrom
Benjtalkshow:wallet-verification-system
Feb 2, 2026
Merged

Integrate Wallet Verification System#139
MPSxDev merged 1 commit into
PayStell:mainfrom
Benjtalkshow:wallet-verification-system

Conversation

@Benjtalkshow

@Benjtalkshow Benjtalkshow commented Jan 27, 2026

Copy link
Copy Markdown
Contributor

📌 Pull Request Description

📝 Summary

This PR integrates the backend Wallet Verification System into the frontend application. It enables users to securely connect their Stellar wallets and verify ownership through an email-based OTP (One-Time Password) flow, delivering a clear, guided, and production-ready verification experience.


🔗 Related Issues

Closes #114


🔄 Changes Made

This PR introduces a full end-to-end wallet verification flow aligned with the backend API and business logic.

Service Layer

  • Refined wallet-verification.ts to fully match backend specifications.
  • Updated request and response interfaces for initiateVerification and verifyWallet.
  • Improved token handling and backend error parsing for more reliable UX feedback.

Components

  • WalletVerification.tsx

    • Fully reworked to support the real verification lifecycle.
    • Integrated with the useWallet hook to detect wallet connection state and retrieve the Stellar public key.
    • Implemented a multi-stage workflow:
      • Connect: Prompt user to connect their Stellar wallet.
      • Initiate: Request backend to send an OTP to the user’s email.
      • OTP: Dedicated 6-digit OTP input with validation and tracking.
      • Verify: Submit verification token and OTP to finalize wallet ownership.
    • Added a showCard prop to optionally disable the outer container for flexible embedding.
    • Simplified UX by removing nested dialogs and adding clear step-by-step indicators.
  • WalletVerificationSection.tsx

    • Updated to consume the new WalletVerification component with showCard={false} for seamless integration into the settings dashboard.
    • Reduced prop-drilling by consolidating handlers internally.

Pages

  • app/dashboard/settings/page.tsx
    • Removed redundant useWallet hooks and local verification state.
    • Centralized toast notifications to prevent duplicate alerts.
    • Added an automatic page refresh after successful verification to sync frontend state with backend data.

🖼️ Current Output

  • Wallet connection and verification flow rendered within the Settings dashboard.
  • OTP entry and verification feedback displayed inline.

📸 Screenshots or 🎥 Loom/video link to be added.


🧪 Testing

  • Verified wallet connection flow using Stellar testnet accounts.
  • Confirmed OTP email delivery and validation.
  • Tested success and failure cases (invalid OTP, expired token, unverified wallet).

✅ Testing Checklist

  • Unit tests added/modified
  • Integration tests performed
  • Manual tests executed
  • All tests pass in CI/CD

Summary by CodeRabbit

Release Notes

  • New Features

    • Redesigned wallet verification process with streamlined steps: connect wallet, enter OTP code, and confirmation.
  • Updates

    • Automatic page refresh implemented after successful wallet verification (3-second delay).
    • Enhanced error handling and user feedback with improved toast notifications throughout the verification flow.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 27, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR refactors the wallet verification system by removing local state management from the settings page, simplifying verification source to rely solely on userData, and substantially redesigning the WalletVerification component to implement a wallet connectivity-driven flow with OTP stages and updated API service types.

Changes

Cohort / File(s) Summary
Settings Page Cleanup
paystell-frontend/src/app/dashboard/settings/page.tsx
Removes useWallet dependency and localWalletVerified state; simplifies isWalletVerified to derive from userData only; eliminates synchronization useEffect; replaces full-page refresh with 3-second window.location.reload() after verification
Wallet Verification Dialog Integration
paystell-frontend/src/components/dashboard/settings/WalletVerificationSection.tsx
Removes user-facing success toast notification; adds showCard={false} prop to WalletVerification component to control wrapper rendering
Wallet Verification Component Refactor
paystell-frontend/src/components/wallet/walletconnect/WalletVerification.tsx
Replaces legacy two-stage flow with wallet connectivity-driven verification; introduces INITIATING and OTP stages; adds async handlers (handleConnect, handleInitiate, handleVerify); adds wallet connection error handling; introduces showCard prop to optionally skip Card UI wrapper; updates step indicators to reflect new flow (connected → OTP → confirmed)
API Service Type Updates
paystell-frontend/src/services/wallet-verification.ts
Changes InitiateWalletVerificationResponse.id from string to number; adds status field; removes verificationCode, createdAt, updatedAt fields; adds optional message field to VerifyWalletResponse

Sequence Diagram

sequenceDiagram
    participant User
    participant WalletExt as Wallet Extension
    participant WalletVerification as WalletVerification Component
    participant API as Backend API
    
    User->>WalletVerification: Click Connect Wallet
    WalletVerification->>WalletExt: Request Connection
    WalletExt->>WalletExt: User Approves
    WalletExt->>WalletVerification: Connected + Address
    
    WalletVerification->>API: handleInitiate() - Request Verification Code
    API->>WalletVerification: verificationToken + status
    
    User->>WalletVerification: Enter OTP Code
    WalletVerification->>API: handleVerify(token, code)
    API->>WalletVerification: Verification Success
    
    WalletVerification->>User: Display Confirmed Status
    WalletVerification->>WalletVerification: Trigger onVerificationComplete()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #110: Implements and modifies the frontend WalletVerification flow with updated API integration and component redesign

Possibly related PRs

Suggested reviewers

  • MPSxDev

Poem

🐰 A wallet now connects with grace,
No local state to clutch and trace,
OTP flows through crystal streams,
Verification fulfills all dreams! ✨
The backend sings in harmony,
A unified wallet symphony! 🎼

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Integrate Wallet Verification System' accurately and concisely describes the main change in the pull request, which refactors and integrates the backend wallet verification system throughout the frontend codebase.
Linked Issues check ✅ Passed The pull request successfully implements the core requirements from issue #114: wallet connection UI, backend API integration with initiateVerification and verifyWallet endpoints, multi-stage verification components with OTP support, proper state management, error handling with toasts, and testing via manual Stellar testnet verification.
Out of Scope Changes check ✅ Passed All changes are directly related to integrating the wallet verification system; no out-of-scope modifications were introduced. Changes focus on service interfaces, component refactoring for the verification flow, settings page cleanup, and internal UX improvements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@paystell-frontend/src/components/wallet/walletconnect/WalletVerification.tsx`:
- Around line 247-252: The CONNECT-stage verify button currently calls
handleInitiate but does nothing if userId is undefined; update the UI and
handler to guard against this by disabling or preventing the click when userId
is missing and adding an explicit check inside handleInitiate (or the initiating
function) that returns early and surfaces an error/notification. Specifically,
in the block handling stage === STAGES.CONNECT around the Button, bind a
disabled prop (or hidden/tooltip) based on !userId and/or avoid calling
handleInitiate when userId is falsy, and add a userId presence check at the
start of handleInitiate to early-return and trigger an error toast/log so the
no-op is not silent.
- Around line 260-270: The OTP input currently accepts any characters; update
the Input onChange handler for the element with id="code" (where value={code}
and setCode is called) to strip non-digit characters and only allow up to 6
digits (e.g., filter e.target.value to digits before calling setCode), and
ensure the Verify button (onClick={handleVerify}) remains disabled unless
code.length === 6 and consists only of digits; this enforces client-side
numeric-only OTP input and prevents invalid submissions to the backend.
- Around line 272-274: In WalletVerification.tsx update the Button onClick that
calls setStage(STAGES.CONNECT) so it also clears any lingering verification
state before restarting (e.g. reset token/code/error and related flags in this
component) by invoking the appropriate setters (for example setToken(null),
setVerificationCode(''), setVerificationError(null) or whatever state variables
are used) so a new connect attempt starts with a clean state; keep the call to
setStage(STAGES.CONNECT) as well.
🧹 Nitpick comments (1)
paystell-frontend/src/components/wallet/walletconnect/WalletVerification.tsx (1)

136-139: Remove the no-op effect.
It currently does nothing and adds noise.

♻️ Proposed cleanup
-useEffect(() => {
-  if (stage === STAGES.CONNECT && isConnected && publicKey) {
-  }
-}, [isConnected, publicKey, stage]);

Comment on lines +247 to +252
if (stage === STAGES.CONNECT) {
return (
<Button className="w-full" onClick={handleInitiate}>
<MdShield className="w-4 h-4 mr-2" /> Verify Wallet Ownership
</Button>
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against a silent no-op when userId is missing.
Right now the button can be clicked and nothing happens if userId is undefined.

✅ Proposed guard
-    if (stage === STAGES.CONNECT) {
-      return (
-        <Button className="w-full" onClick={handleInitiate}>
+    if (stage === STAGES.CONNECT) {
+      return (
+        <Button className="w-full" onClick={handleInitiate} disabled={!userId}>
           <MdShield className="w-4 h-4 mr-2" /> Verify Wallet Ownership
         </Button>
       );
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (stage === STAGES.CONNECT) {
return (
<Button className="w-full" onClick={handleInitiate}>
<MdShield className="w-4 h-4 mr-2" /> Verify Wallet Ownership
</Button>
);
if (stage === STAGES.CONNECT) {
return (
<Button className="w-full" onClick={handleInitiate} disabled={!userId}>
<MdShield className="w-4 h-4 mr-2" /> Verify Wallet Ownership
</Button>
);
}
🤖 Prompt for AI Agents
In `@paystell-frontend/src/components/wallet/walletconnect/WalletVerification.tsx`
around lines 247 - 252, The CONNECT-stage verify button currently calls
handleInitiate but does nothing if userId is undefined; update the UI and
handler to guard against this by disabling or preventing the click when userId
is missing and adding an explicit check inside handleInitiate (or the initiating
function) that returns early and surfaces an error/notification. Specifically,
in the block handling stage === STAGES.CONNECT around the Button, bind a
disabled prop (or hidden/tooltip) based on !userId and/or avoid calling
handleInitiate when userId is falsy, and add a userId presence check at the
start of handleInitiate to early-return and trigger an error toast/log so the
no-op is not silent.

Comment on lines +260 to +270
<Input
id="code"
placeholder="Enter 6-digit code"
value={code}
onChange={(e) => setCode(e.target.value)}
className="text-center tracking-widest text-lg font-bold"
maxLength={6}
/>
</div>
<Button className="w-full" onClick={handleVerify} disabled={code.length < 6}>
<MdCheckCircle className="w-4 h-4 mr-2" /> Verify Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

OTP input should enforce digits.
Currently any characters are accepted; backend will reject, but client-side validation should prevent that.

✅ Proposed validation
             <Input
               id="code"
               placeholder="Enter 6-digit code"
               value={code}
-              onChange={(e) => setCode(e.target.value)}
+              onChange={(e) => setCode(e.target.value.replace(/\D/g, ''))}
               className="text-center tracking-widest text-lg font-bold"
               maxLength={6}
+              inputMode="numeric"
+              pattern="\d*"
             />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Input
id="code"
placeholder="Enter 6-digit code"
value={code}
onChange={(e) => setCode(e.target.value)}
className="text-center tracking-widest text-lg font-bold"
maxLength={6}
/>
</div>
<Button className="w-full" onClick={handleVerify} disabled={code.length < 6}>
<MdCheckCircle className="w-4 h-4 mr-2" /> Verify Code
<Input
id="code"
placeholder="Enter 6-digit code"
value={code}
onChange={(e) => setCode(e.target.value.replace(/\D/g, ''))}
className="text-center tracking-widest text-lg font-bold"
maxLength={6}
inputMode="numeric"
pattern="\d*"
/>
</div>
<Button className="w-full" onClick={handleVerify} disabled={code.length < 6}>
<MdCheckCircle className="w-4 h-4 mr-2" /> Verify Code
🤖 Prompt for AI Agents
In `@paystell-frontend/src/components/wallet/walletconnect/WalletVerification.tsx`
around lines 260 - 270, The OTP input currently accepts any characters; update
the Input onChange handler for the element with id="code" (where value={code}
and setCode is called) to strip non-digit characters and only allow up to 6
digits (e.g., filter e.target.value to digits before calling setCode), and
ensure the Verify button (onClick={handleVerify}) remains disabled unless
code.length === 6 and consists only of digits; this enforces client-side
numeric-only OTP input and prevents invalid submissions to the backend.

Comment on lines +272 to 274
<Button variant="ghost" className="w-full text-xs" onClick={() => setStage(STAGES.CONNECT)}>
Change Wallet or Restart
</Button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reset token/code/error when restarting.
Otherwise the prior code/token can linger and confuse the next attempt.

✅ Proposed reset
-          <Button variant="ghost" className="w-full text-xs" onClick={() => setStage(STAGES.CONNECT)}>
+          <Button
+            variant="ghost"
+            className="w-full text-xs"
+            onClick={() => {
+              setStage(STAGES.CONNECT);
+              setCode('');
+              setVerificationToken(null);
+              setError(null);
+              localStorage.removeItem('wallet_verification_token');
+            }}
+          >
             Change Wallet or Restart
           </Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button variant="ghost" className="w-full text-xs" onClick={() => setStage(STAGES.CONNECT)}>
Change Wallet or Restart
</Button>
<Button
variant="ghost"
className="w-full text-xs"
onClick={() => {
setStage(STAGES.CONNECT);
setCode('');
setVerificationToken(null);
setError(null);
localStorage.removeItem('wallet_verification_token');
}}
>
Change Wallet or Restart
</Button>
🤖 Prompt for AI Agents
In `@paystell-frontend/src/components/wallet/walletconnect/WalletVerification.tsx`
around lines 272 - 274, In WalletVerification.tsx update the Button onClick that
calls setStage(STAGES.CONNECT) so it also clears any lingering verification
state before restarting (e.g. reset token/code/error and related flags in this
component) by invoking the appropriate setters (for example setToken(null),
setVerificationCode(''), setVerificationError(null) or whatever state variables
are used) so a new connect attempt starts with a clean state; keep the call to
setStage(STAGES.CONNECT) as well.

@Benjtalkshow

Copy link
Copy Markdown
Contributor Author

Hi @MPSxDev
Kindly review this PR

@MPSxDev
MPSxDev merged commit 64098aa into PayStell:main Feb 2, 2026
4 of 6 checks passed
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