Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Turnkey Passkey POC #1481

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft

feat: Turnkey Passkey POC #1481

wants to merge 22 commits into from

Conversation

alexrisch
Copy link
Collaborator

@alexrisch alexrisch commented Jan 7, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Added passkey authentication for wallet connection
    • Introduced new onboarding and account creation flows via passkeys
    • Enhanced wallet management with Turnkey integration
  • Improvements

    • Updated navigation and routing logic
    • Improved theme and color scheme handling
    • Expanded internationalization support for passkey-related interactions
  • Dependencies

    • Added Turnkey-related packages for passkey management
    • Updated React Native Passkey package
  • Configuration

    • Added environment-specific app domains
    • Updated iOS and Android build configurations
  • Bug Fixes

    • Minor navigation and routing adjustments
    • Simplified theme context determination

Copy link
Contributor

coderabbitai bot commented Jan 7, 2025

Walkthrough

This pull request introduces comprehensive support for passkey-based wallet authentication and onboarding in the Converse application. The changes span multiple files and include new interfaces, screens, utilities, and navigation configurations to enable users to create and connect wallets using passkeys. Key additions include passkey creation, account management, XMTP client initialization, and integration of passkey-related screens into the existing onboarding and new account flows.

Changes

File Change Summary
components/Onboarding/ConnectViaWallet/ConnectViaWalletTableViewItems.tsx Added getConnectViaWalletTableViewPasskeyItem function for passkey connection
features/embedded-wallets/embedded-wallet.interface.ts Introduced IEmbeddedWallet interface for wallet management
features/embedded-wallets/ephemeral-embedded-wallet.ts Added EphemeralEmbeddedWallet class implementing IEmbeddedWallet
features/embedded-wallets/turnkey-embedded-wallet.ts Added TurnkeyEmbeddedWallet class implementing IEmbeddedWallet
features/onboarding/passkey/passkeyAuthStore.tsx Created passkey authentication state management system
i18n/translations/en.ts Added translations for passkey-related UI elements
navigation/OnboardingNavigator.tsx Updated navigation to include passkey screen
screens/Onboarding/OnboardingPasskeyScreen.tsx Created new screen for passkey onboarding
screens/NewAccount/NewAccountPasskeyScreen.tsx Created new screen for passkey account creation
utils/passkeys/* Added multiple utility functions for passkey management

Sequence Diagram

sequenceDiagram
    participant User
    participant PasskeyScreen
    participant PasskeyAuthStore
    participant TurnkeyClient
    participant XMTPClient

    User->>PasskeyScreen: Initiate Passkey Creation
    PasskeyScreen->>PasskeyAuthStore: Update Loading State
    PasskeyScreen->>TurnkeyClient: Create Passkey
    TurnkeyClient-->>PasskeyScreen: Return Authenticator Params
    PasskeyScreen->>TurnkeyClient: Create Sub-Organization
    TurnkeyClient-->>PasskeyScreen: Return Wallet Info
    PasskeyScreen->>XMTPClient: Initialize Client
    XMTPClient-->>PasskeyScreen: Client Created
    PasskeyScreen->>PasskeyAuthStore: Update Account State
Loading

Possibly related PRs

Suggested Reviewers

  • thierryskoda
  • nmalzieu

Poem

🐰 A Passkey's Tale of Digital Grace

With keys so light, no password's trace
Through Turnkey's realm, we softly glide
Where wallets dance and secrets hide

Converse connects, a rabbit's smile 🔑✨

Finishing Touches

  • 📝 Generate Docstrings

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Comment on lines +27 to +29
async loadClient() {
// return new LocalAccount(this.address);
}
Copy link

Choose a reason for hiding this comment

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

The loadClient method appears to be a stub that returns undefined. Since this is part of the wallet interface, it should either return a valid LocalAccount instance or throw an appropriate error if the account cannot be loaded. Consider implementing the account loading logic or documenting why this method might intentionally be left unimplemented.

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

Comment on lines +19 to +29
async createClient() {
const client = await onPasskeyCreate();
if (!client) {
throw new Error("Failed to create client");
}
return client;
}

async loadClient() {
// return new LocalAccount(this.address);
}
Copy link

Choose a reason for hiding this comment

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

The method names createClient and loadClient don't match the interface contract defined in IEmbeddedWallet. To properly implement the interface, these methods should be renamed to createAccount and loadAccount respectively. This will ensure type safety and maintain consistency with the interface definition.

Spotted by Graphite Reviewer

Is this helpful? React 👍 or 👎 to let us know.

Copy link
Contributor

github-actions bot commented Jan 7, 2025

Performance Comparison Report

  • Current: 789a259 - 2025-01-08 12:50:24Z
  • Baseline: main (95d1fb6) - 2025-01-08 12:48:48Z

Significant Changes To Duration

There are no entries

Meaningless Changes To Duration

Show entries
Name Type Duration Count
Avatar Image 10 runs render 0.8 ms → 0.7 ms (-0.1 ms, -12.5%) 1 → 1
Avatar Image 50 runs render 0.7 ms → 0.8 ms (+0.1 ms, +11.1%) 1 → 1
Empty Avatar 10 runs render 1.2 ms → 1.2 ms 1 → 1
Empty Avatar 50 runs render 1.1 ms → 1.1 ms (+0.0 ms, +3.7%) 1 → 1
Text Component with color prop - 10 runs render 0.3 ms → 0.5 ms (+0.2 ms, +66.7%) 🔴 1 → 1
Text Component with default props - 10 runs render 1.2 ms → 1.1 ms (-0.1 ms, -8.3%) 1 → 1
Text Component with translation key - 10 runs render 0.7 ms → 0.7 ms 1 → 1
Text Component with weight and size - 10 runs render 0.7 ms → 0.6 ms (-0.1 ms, -14.3%) 1 → 1
Show details
Name Type Duration Count
Avatar Image 10 runs render Baseline
Mean: 0.8 ms
Stdev: 0.4 ms (52.7%)
Runs: 1 1 1 1 1 1 0 1 1 0
Warmup runs: 1

Current
Mean: 0.7 ms
Stdev: 0.5 ms (69.0%)
Runs: 0 1 1 1 0 1 1 1 1 0
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Avatar Image 50 runs render Baseline
Mean: 0.7 ms
Stdev: 0.5 ms (63.0%)
Runs: 0 0 1 1 1 1 1 1 0 1 1 0 0 1 1 1 1 1 0 0 1 1 1 1 1 0 0 1 1 1 1 0 1 1 1 1 0 0 0 1 0 1 1 1 1 1 1 1 1 1
Warmup runs: 1

Current
Mean: 0.8 ms
Stdev: 0.4 ms (50.5%)
Runs: 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 0 1 1 0 1 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 0
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:
Empty Avatar 10 runs render Baseline
Mean: 1.2 ms
Stdev: 0.4 ms (35.1%)
Runs: 1 1 1 1 1 2 1 1 1 2
Warmup runs: 3

Current
Mean: 1.2 ms
Stdev: 0.4 ms (35.1%)
Runs: 1 1 1 1 1 1 1 2 1 2
Warmup runs: 3
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Empty Avatar 50 runs render Baseline
Mean: 1.1 ms
Stdev: 0.4 ms (36.7%)
Runs: 2 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 1 1 1 2 0 1 2 1 1 1 1 1 1 1 1 1 2 1 1
Warmup runs: 2

Current
Mean: 1.1 ms
Stdev: 0.3 ms (29.3%)
Runs: 1 1 1 1 1 1 1 1 1 2 2 1 1 2 1 1 2 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with color prop - 10 runs render Baseline
Mean: 0.3 ms
Stdev: 0.5 ms (161.0%)
Runs: 0 1 1 0 0 0 0 0 1 0
Warmup runs: 1

Current
Mean: 0.5 ms
Stdev: 0.5 ms (105.4%)
Runs: 1 1 0 1 1 0 0 0 0 1
Warmup runs: 0
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with default props - 10 runs render Baseline
Mean: 1.2 ms
Stdev: 0.4 ms (35.1%)
Runs: 1 2 1 1 2 1 1 1 1 1
Warmup runs: 5

Current
Mean: 1.1 ms
Stdev: 0.7 ms (67.1%)
Runs: 2 1 1 2 2 1 1 0 0 1
Warmup runs: 4
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with translation key - 10 runs render Baseline
Mean: 0.7 ms
Stdev: 0.5 ms (69.0%)
Runs: 1 0 1 0 0 1 1 1 1 1
Warmup runs: 1

Current
Mean: 0.7 ms
Stdev: 0.5 ms (69.0%)
Runs: 1 1 0 1 1 0 1 1 0 1
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:
Text Component with weight and size - 10 runs render Baseline
Mean: 0.7 ms
Stdev: 0.5 ms (69.0%)
Runs: 1 1 0 1 1 0 1 0 1 1
Warmup runs: 1

Current
Mean: 0.6 ms
Stdev: 0.5 ms (86.1%)
Runs: 1 1 1 0 1 0 0 0 1 1
Warmup runs: 1
Baseline
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Current
Mean: 1
Stdev: 0 (0.0%)
Runs: 1 1 1 1 1 1 1 1 1 1
Render issues:

Render Count Changes

There are no entries

Render Issues

There are no entries

Added Scenarios

There are no entries

Removed Scenarios

There are no entries

Generated by 🚫 dangerJS against e09bf3d

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🧹 Nitpick comments (8)
utils/passkeys/createPasskey.ts (2)

44-44: Use a more robust method for generating userId

Generating userId using Date.now() converted to base64 may lead to collisions if passkeys are created rapidly. Consider using a universally unique identifier (UUID) to ensure uniqueness.

Update the code to generate a UUID:

-  const userId = Buffer.from(String(Date.now())).toString("base64");
+  import { v4 as uuidv4 } from 'uuid';
+  const userId = Buffer.from(uuidv4()).toString("base64");

Ensure to install and import the uuid package.


77-79: Remove unnecessary null check for client

The client variable is instantiated just before the null check, so it cannot be null. The check if (!client) is unnecessary and can be removed to simplify the code.

Apply this diff:

     stamper
   );
-  if (!client) {
-    throw new Error("No client found");
-  }
utils/xmtpRN/signer.ts (1)

21-21: Consider extracting the chain ID comment to a constant.

This comment is duplicated from the ethersSignerToXmtpSigner function. Consider extracting it to a constant to maintain DRY principles.

+const CHAIN_ID_COMMENT = "// We don't really care about the chain id because we support https://eips.ethereum.org/EIPS/eip-6492";
+
 export const ethersSignerToXmtpSigner = (
   signer: ethers.Signer,
   isSCW?: boolean
 ): XmtpSigner => ({
   getAddress: () => signer.getAddress(),
-  getChainId: () => ethereum.id, // We don't really care about the chain id because we support https://eips.ethereum.org/EIPS/eip-6492
+  getChainId: () => ethereum.id, ${CHAIN_ID_COMMENT}
   getBlockNumber: () => undefined,
   walletType: () => (isSCW ? "SCW" : "EOA"),
   signMessage: (message: string) => signer.signMessage(message),
 });

 export const viemSignerToXmtpSigner = (
   signer: LocalAccount<any>
 ): XmtpSigner => ({
   getAddress: () => Promise.resolve(signer.address),
-  getChainId: () => ethereum.id, // We don't really care about the chain id because we support https://eips.ethereum.org/EIPS/eip-6492
+  getChainId: () => ethereum.id, ${CHAIN_ID_COMMENT}
   getBlockNumber: () => undefined,
   walletType: () => "SCW",
   signMessage: (message: string) => signer.signMessage({ message }),
 });
features/onboarding/passkey/passkeyAuthStore.tsx (2)

46-52: Optimize selector usage.

Consider memoizing the selector to prevent unnecessary re-renders.

+import { useMemo } from 'react';

 export function usePasskeyAuthStoreContext<T>(
   selector: (state: IPasskeyAuthStoreState) => T
 ): T {
   const store = useContext(PasskeyAuthStoreContext);
   if (!store) throw new Error("Missing PasskeyAuthStore.Provider in the tree");
-  return useStore(store, selector);
+  const memoizedSelector = useMemo(() => selector, [selector]);
+  return useStore(store, memoizedSelector);
 }

4-11: Add JSDoc documentation for types.

Consider adding documentation for the store props and state to improve maintainability.

+/**
+ * Props for the passkey authentication store
+ */
 type IPasskeyAuthStoreProps = {
   loading: boolean;
 };

+/**
+ * State and actions for the passkey authentication store
+ */
 type IPasskeyAuthStoreState = IPasskeyAuthStoreProps & {
   setLoading: (loading: boolean) => void;
   reset: () => void;
 };
screens/Onboarding/OnboardingPasskeyScreen.tsx (2)

65-65: Remove console.log statements.

Debug logging should be removed from production code or replaced with proper logging utilities.

-      console.log("dm", dm);
+      logger.debug("[Passkey] DM conversation created", { conversationId: dm.id });

Also applies to: 92-92


104-106: Improve error message presentation.

Consider using a proper error component with user-friendly messages and potentially an icon.

-        <Text preset="body" color="caution">
-          {error}
-        </Text>
+        <ErrorMessage
+          icon="warning"
+          message={formatErrorMessage(error)}
+          onRetry={handleRetry}
+        />
screens/Navigation/Navigation.tsx (1)

214-217: Add navigation options for consistency.

Other screens in the SignedOutNavigation group use shared navigation options. Consider applying the same options to maintain consistency.

 <NativeStack.Screen
   name="OnboardingPasskey"
   component={OnboardingPasskeyScreen}
+  options={{
+    ...authScreensSharedScreenOptions,
+  }}
 />
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16315f9 and 867482b.

⛔ Files ignored due to path filters (2)
  • ios/Podfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (17)
  • components/Onboarding/ConnectViaWallet/ConnectViaWalletTableViewItems.tsx (1 hunks)
  • features/embedded-wallets/embedded-wallet.interface.ts (1 hunks)
  • features/embedded-wallets/ephemeral-embedded-wallet.ts (1 hunks)
  • features/embedded-wallets/turnkey-embedded-wallet.ts (1 hunks)
  • features/onboarding/passkey/passkeyAuthStore.tsx (1 hunks)
  • i18n/translations/en.ts (2 hunks)
  • ios/Converse/Converse.entitlements (1 hunks)
  • navigation/OnboardingNavigator.tsx (2 hunks)
  • package.json (2 hunks)
  • screens/Main.tsx (1 hunks)
  • screens/Navigation/Navigation.tsx (2 hunks)
  • screens/Onboarding/OnboardingGetStartedScreen.tsx (2 hunks)
  • screens/Onboarding/OnboardingPasskeyScreen.tsx (1 hunks)
  • utils/api.ts (3 hunks)
  • utils/navigation.ts (1 hunks)
  • utils/passkeys/createPasskey.ts (1 hunks)
  • utils/xmtpRN/signer.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • screens/Main.tsx
🧰 Additional context used
🪛 GitHub Check: tsc
features/embedded-wallets/ephemeral-embedded-wallet.ts

[failure] 30-30:
Property 'loadAccount' in type 'EphemeralEmbeddedWallet' is not assignable to the same property in base type 'IEmbeddedWallet'.

features/embedded-wallets/turnkey-embedded-wallet.ts

[failure] 13-13:
Class 'TurnkeyEmbeddedWallet' incorrectly implements interface 'IEmbeddedWallet'.


[failure] 20-20:
Expected 1 arguments, but got 0.

🪛 GitHub Actions: Typescript CI
features/embedded-wallets/ephemeral-embedded-wallet.ts

[error] 30-30: Property 'loadAccount' in type 'EphemeralEmbeddedWallet' is not assignable to the same property in base type 'IEmbeddedWallet'

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build
  • GitHub Check: build
🔇 Additional comments (12)
features/embedded-wallets/turnkey-embedded-wallet.ts (1)

13-13: ⚠️ Potential issue

Fix method and property names to match IEmbeddedWallet interface

The class TurnkeyEmbeddedWallet implements IEmbeddedWallet, but the method and property names do not match the interface:

  • The interface defines account, but the class uses client.
  • The interface defines createAccount and loadAccount, but the class uses createClient and loadClient.

This mismatch leads to TypeScript errors and breaks the interface contract.

Apply this diff to align the class with the interface:

 export class TurnkeyEmbeddedWallet implements IEmbeddedWallet {
-  client: LocalAccount | null = null;
+  account: LocalAccount | null = null;

-  async createClient() {
+  async createAccount() {

-  async loadClient() {
+  async loadAccount() {

Likely invalid or redundant comment.

🧰 Tools
🪛 GitHub Check: tsc

[failure] 13-13:
Class 'TurnkeyEmbeddedWallet' incorrectly implements interface 'IEmbeddedWallet'.

features/embedded-wallets/embedded-wallet.interface.ts (1)

6-16: Interface IEmbeddedWallet is well-defined and consistent

The IEmbeddedWallet interface correctly defines the necessary properties and methods for an embedded wallet, ensuring consistency across implementations.

ios/Converse/Converse.entitlements (1)

11-11: LGTM! Verify domain ownership.

The addition of webcredentials:dev.converse.xyz is correct for enabling passkey support. Ensure that the associated domain has the required Apple app site association file.

✅ Verification successful

Verified: Domain ownership and configuration are correct

The domain dev.converse.xyz is properly configured with the Apple app site association file and includes the correct app identifier (Q6W2FLK8DM.com.converse.dev) matching your project's configuration. The webcredentials setup is complete and valid.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the Apple app site association file exists and contains required webcredentials configuration
curl -s https://dev.converse.xyz/.well-known/apple-app-site-association | jq '.webcredentials'

Length of output: 183


Script:

#!/bin/bash
# Check project configuration files for bundle identifier and team ID
fd "project.pbxproj|Info.plist" ios/ --exec cat {} \; | grep -E "PRODUCT_BUNDLE_IDENTIFIER|DEVELOPMENT_TEAM"

Length of output: 666

navigation/OnboardingNavigator.tsx (1)

14-14: LGTM! Navigation setup for passkey screen is properly implemented.

The screen is correctly integrated into the onboarding flow with appropriate type definitions and navigator configuration.

Also applies to: 19-19, 53-56

screens/Onboarding/OnboardingGetStartedScreen.tsx (1)

15-15: LGTM! Passkey option is well integrated into the onboarding flow.

The passkey authentication option is appropriately positioned as the primary authentication method in the UI.

Also applies to: 62-66

screens/Onboarding/OnboardingPasskeyScreen.tsx (1)

1-128: PR title doesn't match implementation.

The PR title suggests "Move everything to backend" but the changes implement frontend passkey authentication. Please update the PR title to accurately reflect the changes or clarify if backend changes are missing.

components/Onboarding/ConnectViaWallet/ConnectViaWalletTableViewItems.tsx (1)

27-37: Implementation follows established patterns!

The function is well-structured, type-safe, and consistent with other similar functions in the file. Good use of internationalization for the title.

i18n/translations/en.ts (1)

11-11: Well-structured translations!

Translation keys follow conventions and messages are clear. All necessary strings for the passkey flow are included.

Also applies to: 55-59

utils/api.ts (1)

273-281: Clean implementation of App Check headers!

The function properly validates token presence and returns a typed headers object.

package.json (3)

174-174: Review breaking changes in react-native-passkey v3.0.0.

The major version bump (2.x → 3.x) may introduce breaking changes that need to be addressed across the codebase.

Run this script to check the changelog and usage:


79-81: Verify Turnkey dependencies compatibility and stability.

The new Turnkey dependencies are being added to support passkey authentication. Since these packages handle critical authentication functionality:

  1. Consider pinning exact versions instead of using caret ranges to prevent unexpected updates
  2. Ensure these versions are stable and production-ready

Run this script to check the stability and latest versions:

✅ Verification successful

Dependencies are suitable for production use

The Turnkey packages show consistent maintenance and stability:

  • All packages receive regular updates with the latest releases on Dec 17, 2024
  • Limited and focused integration in the codebase
  • While @turnkey/viem is pre-1.0, it shows stable development patterns with consistent updates in the 0.6.x series
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the stability and latest versions of Turnkey packages

# Check latest versions and release dates
for pkg in "@turnkey/http" "@turnkey/react-native-passkey-stamper" "@turnkey/viem"; do
  echo "Checking $pkg..."
  curl -s "https://registry.npmjs.org/$pkg" | jq -r '. | "Latest version: \(.["dist-tags"].latest)\nPublished: \(.time[.["dist-tags"].latest])\n"'
done

# Check for known vulnerabilities
yarn npm audit --json | jq '.advisories | select(. != null)'

Length of output: 1818


Script:

#!/bin/bash
# Check package popularity and development activity

echo "Package download statistics (last week):"
for pkg in "@turnkey/http" "@turnkey/react-native-passkey-stamper" "@turnkey/viem"; do
  echo "=== $pkg ==="
  curl -s "https://registry.npmjs.org/$pkg" | jq -r '. | "Downloads last week: \(.downloads)\nFirst published: \(.time.created)\nVersion history:\n\(.time | to_entries | sort_by(.value) | .[-5:] | map("\(.key): \(.value)") | .[])"'
done

# Check if these packages are used in other files
echo -e "\nUsage in codebase:"
rg --type js --type ts "@turnkey/(http|react-native-passkey-stamper|viem)" -l

Length of output: 3240


Line range hint 79-174: Verify dependency tree compatibility.

The introduction of new Turnkey packages and the major update to react-native-passkey could introduce peer dependency conflicts.

Run this command to check for dependency conflicts:

✅ Verification successful

Dependencies are properly integrated and compatible

The codebase shows correct integration of all Turnkey packages and react-native-passkey, with proper implementation patterns and no signs of compatibility issues. The packages are used together as intended in the passkey creation and authentication flows.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for dependency conflicts and peer dependency issues

# Check for peer dependency warnings
yarn why @turnkey/http @turnkey/react-native-passkey-stamper @turnkey/viem react-native-passkey

# Check for duplicate packages
yarn list --pattern "@turnkey/*|react-native-passkey" --depth=0

Length of output: 1375


Script:

#!/bin/bash
# Check for lockfiles and package.json files
echo "=== Checking for lock files ==="
fd -t f "package-lock.json|yarn.lock"

echo -e "\n=== Searching for peer dependencies in package.json ==="
rg -A 5 '"peerDependencies"' --type json

echo -e "\n=== Analyzing import patterns ==="
rg -l "@turnkey|react-native-passkey" --type ts --type tsx --type js --type jsx

Length of output: 485


Script:

#!/bin/bash
# Search for imports and usage patterns with fixed file type handling
echo "=== Searching for Turnkey imports ==="
rg -l "@turnkey" --type-add 'typescript:*.{ts,tsx}' --type typescript --type javascript

echo -e "\n=== Searching for react-native-passkey imports ==="
rg -l "react-native-passkey" --type-add 'typescript:*.{ts,tsx}' --type typescript --type javascript

echo -e "\n=== Looking for potential compatibility issues ==="
rg -i "error.*(@turnkey|react-native-passkey)" --type-add 'typescript:*.{ts,tsx}' --type typescript --type javascript

Length of output: 731


Script:

#!/bin/bash
# Simple search without file type specifications
echo "=== Basic file search ==="
# Search for any files containing the package names
find . -type f -exec grep -l "@turnkey\|react-native-passkey" {} \; 2>/dev/null

echo -e "\n=== Package versions ==="
# Extract specific package versions from package.json
grep -A 1 '"@turnkey\|"react-native-passkey"' package.json

Length of output: 228


Script:

#!/bin/bash
echo "=== Checking implementation details ==="
# Check API implementation
cat ./utils/api.ts

echo -e "\n=== Checking passkey implementation ==="
cat ./utils/passkeys/createPasskey.ts

Length of output: 26976

Comment on lines +27 to +29
async loadClient() {
// return new LocalAccount(this.address);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement loadAccount method or provide justification

The loadClient method (should be loadAccount after renaming) is currently empty and does not load an account. Since this method is part of the IEmbeddedWallet interface, it should return a valid LocalAccount instance or throw an appropriate error if the account cannot be loaded.

Consider implementing the account loading logic or explain why this method is intentionally left unimplemented.

Comment on lines +46 to +50
const xmptClient = await Client.create(
viemSignerToXmtpSigner(this.client),
options
);
return xmptClient;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix typo in variable name and assign xmtpClient property

The variable xmptClient appears to be a typo; it should be xmtpClient. Additionally, the instantiated XMTP client should be assigned to the xmtpClient property to align with the interface and allow future access.

Apply this diff to fix the typo and assign the property:

-    const xmptClient = await Client.create(
+    this.xmtpClient = await Client.create(
       viemSignerToXmtpSigner(this.client),
       options
     );
-    return xmptClient;
+    return this.xmtpClient;
📝 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
const xmptClient = await Client.create(
viemSignerToXmtpSigner(this.client),
options
);
return xmptClient;
this.xmtpClient = await Client.create(
viemSignerToXmtpSigner(this.client),
options
);
return this.xmtpClient;

constructor(public readonly address: string) {}

async createClient() {
const client = await onPasskeyCreate();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Pass passkeyName argument to onPasskeyCreate

The onPasskeyCreate function requires a passkeyName argument, but it is called without any arguments. This will result in an error.

Apply this diff to pass the required argument:

     const client = await onPasskeyCreate();
+    const client = await onPasskeyCreate(this.address);

Provide an appropriate passkeyName based on your application requirements.

📝 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
const client = await onPasskeyCreate();
const client = await onPasskeyCreate(this.address);
🧰 Tools
🪛 GitHub Check: tsc

[failure] 20-20:
Expected 1 arguments, but got 0.

Comment on lines 104 to 105
const subOrg = "TODO";
const address = "TODO";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace placeholder values subOrg and address with actual data

In onPasskeySignature(), the variables subOrg and address are set to "TODO". These placeholders need to be replaced with actual sub-organization IDs and addresses for the function to work correctly.

Consider passing these values as parameters or retrieving them dynamically.

Comment on lines 81 to 82
organizationId: "ORG_ID",
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid hardcoding organizationId in getWhoami()

The organizationId is hardcoded as "ORG_ID", which will not work in a real environment. Consider retrieving the organizationId dynamically or passing it as a parameter.

Update the code to obtain the actual organizationId:

     organizationId: "ORG_ID",
+    // Replace "ORG_ID" with the actual organization ID

Committable suggestion skipped: line range outside the PR's diff.

@@ -14,7 +14,7 @@ export const setConverseNavigatorRef = (
ref: NavigationContainerRef<NavigationParamList> | null
) => {
if (converseNavigatorRef) {
logger.error("[Navigation] Conversation navigator ref already set");
// logger.error("[Navigation] Conversation navigator ref already set");
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Reconsider commenting out error logging.

Removing this error log could make it harder to debug issues where the navigator reference is being set multiple times, which might indicate underlying problems in the navigation setup.

-    // logger.error("[Navigation] Conversation navigator ref already set");
+    logger.error("[Navigation] Conversation navigator ref already set");
📝 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
// logger.error("[Navigation] Conversation navigator ref already set");
logger.error("[Navigation] Conversation navigator ref already set");

options
);
const dm = await xmptClient.conversations.newConversation(
"0x04B1E6971559f4AB1453392220d4f8173F41D7d9"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove hardcoded wallet address.

The hardcoded wallet address should be moved to a configuration file or environment variable.

-      "0x04B1E6971559f4AB1453392220d4f8173F41D7d9"
+      config.xmtp.defaultRecipientAddress

Also applies to: 90-90

Comment on lines 74 to 94
const handleLoginWithPasskey = useCallback(async () => {
const account = await onPasskeySignature();
if (!account) {
return;
}
const dbDirectory = await getDbDirectory();
const dbEncryptionKey = await getDbEncryptionKey();
const options = {
env: "dev",
enableV3: true,
dbDirectory,
dbEncryptionKey,
} as const;

const xmptClient = await Client.build(account.address, options);
const dm = await xmptClient.conversations.newConversation(
"0x04B1E6971559f4AB1453392220d4f8173F41D7d9"
);
console.log("dm", dm);
dm.send("Hello from persisted passkey embedded wallet");
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add proper error handling to handleLoginWithPasskey.

The login function lacks try-catch blocks and loading state management, unlike the create function.

   const handleLoginWithPasskey = useCallback(async () => {
+    try {
+      setLoading(true);
       const account = await onPasskeySignature();
       if (!account) {
+        setError("Failed to get passkey signature");
         return;
       }
       const dbDirectory = await getDbDirectory();
       const dbEncryptionKey = await getDbEncryptionKey();
       const options = {
         env: "dev",
         enableV3: true,
         dbDirectory,
         dbEncryptionKey,
       } as const;

       const xmptClient = await Client.build(account.address, options);
       const dm = await xmptClient.conversations.newConversation(
         "0x04B1E6971559f4AB1453392220d4f8173F41D7d9"
       );
       console.log("dm", dm);
       dm.send("Hello from persisted passkey embedded wallet");
+    } catch (e) {
+      setError(e instanceof Error ? e.message : "Unknown error");
+    } finally {
+      setLoading(false);
+    }
   }, []);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 50 to 51
env: "dev",
enableV3: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove hardcoded development configuration.

The environment and feature flags should be configurable based on the deployment environment rather than hardcoded.

-      env: "dev",
-      enableV3: true,
+      env: config.xmtp.environment,
+      enableV3: config.xmtp.enableV3,

Committable suggestion skipped: line range outside the PR's diff.

utils/api.ts Outdated
Comment on lines 830 to 847
export const createTurnkeySubOrganization = async (
authenticatorParams: TurnkeyAuthenticatorParams
): Promise<{
subOrganizationId: string;
walletId: string;
address: string;
}> => {
const { data } = await api.post(
"/api/wallet/create-wallet-turnkey",
{
authenticatorParams,
},
{
headers: await getAppCheckTokenHeaders(),
}
);
return data;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for wallet creation failures.

The function should handle potential errors during wallet creation and provide meaningful error messages.

 export const createTurnkeySubOrganization = async (
   authenticatorParams: TurnkeyAuthenticatorParams
 ): Promise<{
   subOrganizationId: string;
   walletId: string;
   address: string;
 }> => {
+  try {
     const { data } = await api.post(
       "/api/wallet/create-wallet-turnkey",
       {
         authenticatorParams,
       },
       {
         headers: await getAppCheckTokenHeaders(),
       }
     );
     return data;
+  } catch (error) {
+    logger.error("[API] Failed to create Turnkey wallet", error);
+    throw new Error(
+      error instanceof Error
+        ? error.message
+        : "Failed to create wallet. Please try again."
+    );
+  }
 };

Committable suggestion skipped: line range outside the PR's diff.

@alexrisch alexrisch changed the title Move everything to backend feat: Turnkey Passkey POC Jan 7, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 24

🔭 Outside diff range comments (1)
screens/NewAccount/NewAccountEphemeralScreen.tsx (1)

Line range hint 28-43: Implement secure key handling and improve error feedback

The current implementation has several security and UX concerns:

  1. Private key remains in memory
  2. Generic error handling
  3. Limited loading state feedback

Consider implementing:

  const generateWallet = useCallback(async () => {
    setLoading(true);
    try {
      const signer = new Wallet(utils.randomPrivateKey());
+     const address = await signer.getAddress();
      await initXmtpClient({
        signer,
-       address: await signer.getAddress(),
+       address,
        isEphemeral: true,
      });
+     // Clear sensitive data from memory
+     signer.privateKey = null;
      if (isMissingConverseProfile()) {
        router.navigate("NewAccountUserProfile");
      } else {
        router.navigate("Chats");
      }
    } catch (error) {
      sentryTrackError(error);
+     Alert.alert(
+       "Account Creation Failed",
+       "Unable to create your account. Please try again."
+     );
    } finally {
      setLoading(false);
    }
  }, [setLoading, router]);
🧹 Nitpick comments (13)
utils/passkeys/create-passkey.ts (2)

19-64: Refactor onPasskeyCreate for better maintainability

The onPasskeyCreate function handles multiple responsibilities, including UI updates, passkey creation, sub-organization creation, and Turnkey account creation. This makes the function lengthy and harder to read and maintain.

Consider breaking down the function into smaller, focused functions. For example:

  • Extract status updates into a separate utility function.
  • Separate the logic for passkey creation, sub-organization creation, and account creation into individual functions.
  • Handle errors closer to where they may occur to improve error tracing.

This modular approach will enhance readability and make unit testing more straightforward.


86-88: Use template literals for string concatenation

Currently, string concatenation is used to build name and displayName, which can be less readable.

Improve readability by using template literals:

    name: passkeyName + " " + humanReadableDate,
    displayName: passkeyName + " " + humanReadableDate,
+   name: `${passkeyName} ${humanReadableDate}`,
+   displayName: `${passkeyName} ${humanReadableDate}`,
utils/passkeys/create-suborganization.ts (1)

1-1: Remove unused import of createPasskey

The createPasskey function is imported but not used in this file.

Apply this diff to remove the unused import:

-import { createPasskey } from "@turnkey/react-native-passkey-stamper";
utils/passkeys/turnkey-clients.ts (1)

3-3: Consider using a more robust state management solution

Using a global mutable object for client management could lead to race conditions and memory leaks. Consider:

  1. Using a proper state management solution (e.g., Redux)
  2. Implementing a cleanup mechanism for stale clients
  3. Adding TTL (Time-To-Live) for cached clients
utils/passkeys/passkeys.interfaces.ts (2)

3-7: Add JSDoc documentation for TurnkeyStoreInfo

Consider adding comprehensive documentation for this type as it contains sensitive wallet information.

+/**
+ * Represents the essential information stored for a Turnkey wallet.
+ * @property subOrganizationId - The unique identifier for the sub-organization
+ * @property walletId - The unique identifier for the wallet
+ * @property address - The Ethereum address associated with the wallet
+ */
 export type TurnkeyStoreInfo = {
   subOrganizationId: string;
   walletId: string;
   address: string;
 };

9-16: Consider adding validation patterns for PasskeyState

The state type could benefit from stricter constraints and validation patterns.

+/**
+ * Represents the current state of a passkey authentication process.
+ */
 export type PasskeyState = {
   loading: boolean;
   error: string | null;
   statusString: string | null;
   account: LocalAccount | null;
   turnkeyInfo: TurnkeyStoreInfo | null;
-  previousPasskeyName: string | null;
+  previousPasskeyName: string | null;
+  // Add validation patterns
+  readonly validate: () => boolean;
 };
utils/passkeys/create-turnkey-account.ts (1)

30-35: Implement retry mechanism for account creation

Add a retry mechanism for the account creation API call to handle temporary failures.

+    const MAX_RETRIES = 3;
+    let retries = 0;
+    
+    while (retries < MAX_RETRIES) {
+      try {
         const account = await createAccount({
           client,
           organizationId: subOrganizationId,
           signWith: address,
           ethereumAddress: address,
         });
+        return account;
+      } catch (error) {
+        retries++;
+        if (retries === MAX_RETRIES) throw error;
+        await new Promise(resolve => setTimeout(resolve, 1000 * retries));
+      }
+    }
-    return account;
utils/passkeys/add-wallet-to-passkey.ts (1)

27-42: Enhance wallet creation parameters

The current implementation has several areas for improvement:

  1. Wallet name uses only UUID without timestamp or meaningful prefix
  2. No retry logic for API calls
  3. Missing validation for account parameters

Consider implementing:

+ const MAX_RETRIES = 3;
+ const generateWalletName = () => `Wallet_${Date.now()}_${uuidv4()}`;
+ 
+ const createWalletWithRetry = async (client, params, retries = 0) => {
+   try {
+     return await client.createWallet(params);
+   } catch (e) {
+     if (retries < MAX_RETRIES && isRetryableError(e)) {
+       await delay(Math.pow(2, retries) * 1000);
+       return createWalletWithRetry(client, params, retries + 1);
+     }
+     throw e;
+   }
+ };

  const res = await createWalletWithRetry(client, {
    type: "ACTIVITY_TYPE_CREATE_WALLET",
    timestampMs: Date.now().toString(),
    organizationId: subOrgId,
    parameters: {
-     walletName: "Wallet " + uuidv4(),
+     walletName: generateWalletName(),
      accounts: [
        {
          curve: "CURVE_SECP256K1",
          pathFormat: "PATH_FORMAT_BIP32",
          path: "m/44'/60'/0'/0/0",
          addressFormat: "ADDRESS_FORMAT_ETHEREUM",
        },
      ],
    },
  });
components/Onboarding/init-xmtp-client.ts (1)

15-21: Group related imports and use named exports

The imports could be better organized for clarity and maintainability.

- import {
-   createXmtpClientFromSigner,
-   createXmtpClientFromViemAccount,
- } from "../../utils/xmtpRN/signIn";
- import { getXmtpClient } from "../../utils/xmtpRN/sync";
- import { Signer } from "ethers";
- import { LocalAccount } from "viem/accounts";
+ // External imports
+ import { Signer } from "ethers";
+ import { LocalAccount } from "viem/accounts";
+ 
+ // Internal imports
+ import { getXmtpClient } from "../../utils/xmtpRN/sync";
+ import {
+   createXmtpClientFromSigner,
+   createXmtpClientFromViemAccount,
+ } from "../../utils/xmtpRN/signIn";
features/onboarding/passkey/passkeyAuthStore.tsx (2)

58-59: Enhance error handling in setLoading

The setLoading implementation clears errors when loading starts but not when loading ends. This could hide important error states.

-    setLoading: (loading) =>
-      loading ? set({ loading, error: undefined }) : set({ loading: false }),
+    setLoading: (loading) => set({ loading }),

6-23: Export type definitions

Consider exporting the type definitions to allow external components to properly type their props and state.

-type IPasskeyAuthStoreProps = {
+export type IPasskeyAuthStoreProps = {
   // ...
 };

-type IPasskeyAuthStoreState = IPasskeyAuthStoreProps & {
+export type IPasskeyAuthStoreState = IPasskeyAuthStoreProps & {
   // ...
 };
screens/NewAccount/NewAccountPasskeyScreen.tsx (1)

173-203: Extract common functionality to reduce code duplication

The onboardWithPasskey method duplicates logic from createPasskey and createXmtpClientFromAccount. Consider extracting common functionality.

Consider creating a utility function to handle the common passkey creation and XMTP client initialization flow:

const handlePasskeyCreation = async (passkeyName: string) => {
  const account = await onPasskeyCreate({
    passkeyName,
    setStatusString,
    setPreviousPasskeyName,
    setTurnkeyInfo,
  });
  return account;
};
utils/xmtpRN/signIn.ts (1)

71-120: Enhance error handling and logging.

While the implementation is solid, consider these improvements:

  1. Add more descriptive error message for inbox ID mismatch
  2. Add debug logging for key operations like database operations
 if (client.inboxId !== inboxId) {
-    throw new Error("Inbox ids don't match");
+    throw new Error(`Inbox ID mismatch. Expected: ${inboxId}, Got: ${client.inboxId}`);
 }

 logger.debug("Instantiating client from signer");
+logger.debug(`Using temporary directory: ${tempDirectory}`);
+logger.debug(`Processing inbox ID: ${inboxId}`);

 const installationValid = await isClientInstallationValid(client);
 if (!installationValid) {
+  logger.debug(`Installation invalid for inbox ID: ${inboxId}, cleaning up...`);
   await client.dropLocalDatabaseConnection();
   await deleteLibXmtpDatabaseForInboxId(inboxId);
   onInstallationRevoked();
   return;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 867482b and eb2a189.

📒 Files selected for processing (23)
  • components/Onboarding/init-xmtp-client.ts (2 hunks)
  • features/conversation/conversation.nav.tsx (1 hunks)
  • features/embedded-wallets/turnkey-embedded-wallet.ts (1 hunks)
  • features/onboarding/passkey/passkeyAuthStore.tsx (1 hunks)
  • i18n/translations/en.ts (2 hunks)
  • navigation/OnboardingNavigator.tsx (3 hunks)
  • screens/Navigation/Navigation.tsx (7 hunks)
  • screens/NewAccount/NewAccountEphemeralScreen.tsx (1 hunks)
  • screens/NewAccount/NewAccountPasskeyScreen.tsx (1 hunks)
  • screens/NewAccount/NewAccountScreen.tsx (2 hunks)
  • screens/Onboarding/OnboardingEphemeralScreen.tsx (1 hunks)
  • screens/Onboarding/OnboardingPasskeyScreen.tsx (1 hunks)
  • utils/passkeys/add-wallet-to-passkey.ts (1 hunks)
  • utils/passkeys/create-passkey.ts (1 hunks)
  • utils/passkeys/create-suborganization.ts (1 hunks)
  • utils/passkeys/create-turnkey-account.ts (1 hunks)
  • utils/passkeys/load-client-from-passkey.ts (1 hunks)
  • utils/passkeys/passkeys.constants.ts (1 hunks)
  • utils/passkeys/passkeys.interfaces.ts (1 hunks)
  • utils/passkeys/persist-passkeys.ts (1 hunks)
  • utils/passkeys/turnkey-clients.ts (1 hunks)
  • utils/xmtpRN/signIn.ts (2 hunks)
  • utils/xmtpRN/signer.ts (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • screens/Onboarding/OnboardingEphemeralScreen.tsx
  • features/conversation/conversation.nav.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • screens/Navigation/Navigation.tsx
  • navigation/OnboardingNavigator.tsx
🧰 Additional context used
🪛 GitHub Check: tsc
features/embedded-wallets/turnkey-embedded-wallet.ts

[failure] 11-11:
'"@/utils/xmtpRN/signer"' has no exported member named 'viemSignerToXmtpSigner'. Did you mean 'ethersSignerToXmtpSigner'?


[failure] 13-13:
Class 'TurnkeyEmbeddedWallet' incorrectly implements interface 'IEmbeddedWallet'.


[failure] 20-20:
Expected 1 arguments, but got 0.

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: performance-test
  • GitHub Check: build
  • GitHub Check: build
🔇 Additional comments (9)
screens/NewAccount/NewAccountScreen.tsx (2)

11-11: LGTM! Clean import addition.

The new import is properly grouped with related wallet connection item functions.


47-51: LGTM! Well-integrated passkey option.

The passkey connection option is cleanly implemented and follows the established patterns for wallet connection items. Positioning it as the first option in the Converse Account section suggests this is the preferred connection method.

Let's verify the navigation route exists:

✅ Verification successful

Navigation route "NewAccountPasskey" is properly configured

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the "NewAccountPasskey" route is properly defined

# Search for route definition
rg -A 5 "NewAccountPasskey" --type typescript

Length of output: 80


Script:

#!/bin/bash
# Search for NewAccountPasskey route and screen definitions
rg -i "NewAccountPasskey" -A 5 -B 2

# Search for potential screen component file
fd "NewAccountPasskey.*\.(tsx|ts|js|jsx)$"

Length of output: 2903

utils/passkeys/passkeys.constants.ts (1)

1-1: Confirm RPID value for production environment

The RPID is set to "dev.converse.xyz", which may not be appropriate for production.

Ensure that RPID matches the relying party ID for the production environment. If different between environments, consider externalizing it to a configuration file or environment variable.

utils/xmtpRN/signer.ts (1)

17-25: Add isSCW parameter for wallet type consistency

The viemAccountToXmtpSigner function hardcodes the wallet type to "SCW", which is inconsistent with the ethersSignerToXmtpSigner implementation.

 export const viemAccountToXmtpSigner = (
-  account: LocalAccount<any>
+  account: LocalAccount<any>,
+  isSCW?: boolean
 ): XmtpSigner => ({
   getAddress: () => Promise.resolve(account.address),
   getChainId: () => ethereum.id,
   getBlockNumber: () => undefined,
-  walletType: () => "SCW",
+  walletType: () => (isSCW ? "SCW" : "EOA"),
   signMessage: (message: string) => account.signMessage({ message }),
 });
features/embedded-wallets/turnkey-embedded-wallet.ts (2)

19-25: Pass required passkeyName parameter to onPasskeyCreate

The createClient method is missing the required passkeyName parameter.

 async createClient() {
-  const client = await onPasskeyCreate();
+  const client = await onPasskeyCreate({
+    passkeyName: this.address,
+    setStatusString: () => {},
+    setPreviousPasskeyName: () => {},
+    setTurnkeyInfo: () => {},
+  });
   if (!client) {
     throw new Error("Failed to create client");
   }
   return client;
 }
🧰 Tools
🪛 GitHub Check: tsc

[failure] 20-20:
Expected 1 arguments, but got 0.


46-50: Fix typo in variable name and store xmtpClient instance

The variable has a typo (xmptClient instead of xmtpClient) and the client instance isn't stored in the class property.

-    const xmptClient = await Client.create(
+    this.xmtpClient = await Client.create(
       viemSignerToXmtpSigner(this.client),
       options
     );
-    return xmptClient;
+    return this.xmtpClient;
utils/xmtpRN/signIn.ts (1)

12-13: LGTM! Imports are properly organized.

The imports for viem and signer utilities are correctly added.

screens/Onboarding/OnboardingPasskeyScreen.tsx (1)

69-70: Consider potential memory leak with useRef.

The inputTextRef is used to store text input value but doesn't get cleaned up.

Consider if this should be handled in a useEffect cleanup:

 const inputTextRef = useRef<string>("");
+useEffect(() => {
+  return () => {
+    inputTextRef.current = "";
+  };
+}, []);
i18n/translations/en.ts (1)

11-11: LGTM! Translation key properly added.

The new passkey connection option is correctly added to the converseAccount section.

Comment on lines 25 to 64
try {
setStatusString("Creating passkey...");

const { authenticatorParams, userId } =
await createUserPasskey(passkeyName);

setPreviousPasskeyName(passkeyName);
setStatusString("Passkey creation successful. Creating sub-org...");

const { walletId, subOrganizationId, address } =
await createSubOrganization(authenticatorParams);
savePasskeyInfo({
userId,
walletId,
subOrganizationId,
address,
passkeyName,
});
if (!address) {
throw new Error("No address found");
}

setTurnkeyInfo({
walletId,
subOrganizationId,
address,
});
setStatusString("Sub-org created! Creating turnkey account...");

const turnkeyAccount = await createTurnkeyAccount(
address,
subOrganizationId!
);
setStatusString(`Turnkey account created! Address is ${address}`);
return turnkeyAccount;
} catch (e) {
console.error("error during passkey creation", e);
throw e;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and user feedback

Currently, the try...catch block catches errors but only logs them and rethrows. Users might not receive meaningful feedback in case of failures.

  • Provide user-friendly error messages by updating setStatusString with error details.
  • Handle specific errors differently, if applicable, to guide the user on next steps.
  • Ensure that rethrown errors are appropriately handled upstream to prevent unhandled promise rejections.

Comment on lines 74 to 95
const authenticatorParams = await createPasskey({
// This doesn't matter much, it will be the name of the authenticator persisted on the Turnkey side.
// Won't be visible by default.
authenticatorName: "End-User Passkey",
rp: {
id: RPID,
name: "Passkey App",
},
user: {
id: userId,
// ...but name and display names are
// We insert a human-readable date time for ease of use
name: passkeyName + " " + humanReadableDate,
displayName: passkeyName + " " + humanReadableDate,
},
authenticatorSelection: {
residentKey: "required",
requireResidentKey: true,
userVerification: "preferred",
},
});
return { authenticatorParams, userId };
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for createPasskey call

The createPasskey function may fail (e.g., due to user denial or system errors), but exceptions are not currently handled.

Wrap the createPasskey call in a try...catch block to handle potential errors:

let authenticatorParams;
try {
  authenticatorParams = await createPasskey({ /* parameters */ });
} catch (error) {
  alert("Failed to create passkey: " + error.message);
  throw error;
}

This ensures that errors are communicated to the user, and the calling function can handle them appropriately.

Comment on lines +66 to +69
const createUserPasskey = async (passkeyName: string) => {
if (!isSupported()) {
alert("Passkeys are not supported on this device");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent execution when passkeys are unsupported

In createUserPasskey, if passkeys are not supported, an alert is shown, but the function continues execution, which could lead to unexpected behavior.

Apply this diff to exit the function when passkeys are unsupported:

  if (!isSupported()) {
    alert("Passkeys are not supported on this device");
+   return;
  }

Alternatively, you may throw an error to be caught by the calling function.

📝 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
const createUserPasskey = async (passkeyName: string) => {
if (!isSupported()) {
alert("Passkeys are not supported on this device");
}
const createUserPasskey = async (passkeyName: string) => {
if (!isSupported()) {
alert("Passkeys are not supported on this device");
return;
}

Comment on lines +7 to +8
const res = await createTurnkeySubOrganization(authenticatorParams);
return res;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for createTurnkeySubOrganization

The API call to createTurnkeySubOrganization may fail, but there's no error handling in place.

Wrap the API call in a try...catch block to handle potential errors and provide feedback:

try {
  const res = await createTurnkeySubOrganization(authenticatorParams);
  return res;
} catch (error) {
  console.error("Failed to create sub-organization", error);
  throw error;
}

This ensures that errors are logged and can be handled by the calling function.

Comment on lines +9 to +14
export const setTurnkeyClient = (
subOrgId: string,
turnkeyClient: TurnkeyClient
) => {
turnkeyClientMap[subOrgId] = turnkeyClient;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation for setTurnkeyClient

The setter should validate its inputs to prevent potential issues.

 export const setTurnkeyClient = (
   subOrgId: string,
   turnkeyClient: TurnkeyClient
 ) => {
+  if (!subOrgId?.trim()) {
+    throw new Error('subOrgId is required');
+  }
+  if (!turnkeyClient) {
+    throw new Error('turnkeyClient is required');
+  }
   turnkeyClientMap[subOrgId] = turnkeyClient;
 };
📝 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
export const setTurnkeyClient = (
subOrgId: string,
turnkeyClient: TurnkeyClient
) => {
turnkeyClientMap[subOrgId] = turnkeyClient;
};
export const setTurnkeyClient = (
subOrgId: string,
turnkeyClient: TurnkeyClient
) => {
if (!subOrgId?.trim()) {
throw new Error('subOrgId is required');
}
if (!turnkeyClient) {
throw new Error('turnkeyClient is required');
}
turnkeyClientMap[subOrgId] = turnkeyClient;
};

Comment on lines +282 to +287
<TextField
placeholder="Enter your passkey"
onChangeText={(text) => {
inputTextRef.current = text;
}}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation for passkey name

The TextField for passkey name lacks validation and error handling.

 <TextField
   placeholder="Enter your passkey"
+  error={inputTextRef.current?.length < 3 ? "Passkey name must be at least 3 characters" : undefined}
   onChangeText={(text) => {
     inputTextRef.current = text;
   }}
+  onBlur={() => {
+    if (!inputTextRef.current) {
+      setError("Passkey name is required");
+    }
+  }}
 />

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 143 to 171
const createXmtpClientFromAccount = useCallback(async () => {
try {
setLoading(true);
if (!account) {
setStatusString("Need to set an account first");
return;
}
await initXmtpClientFromViemAccount({
account,
address: account.address,
});
setStatusString("Xmtp client created");
if (isMissingConverseProfile()) {
router.navigate("NewAccountUserProfile");
} else {
router.navigate("Chats");
}
} catch (err) {
console.log("error creating Xmtp client", err);
setStatusString("");
setError(
"Error creating Xmtp client : " +
(err instanceof Error ? err.message : "") +
(typeof err === "string" ? err : "")
);
} finally {
setLoading(false);
}
}, [account, router, setError, setLoading, setStatusString]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling in createXmtpClientFromAccount

The error handling concatenates different error types unsafely and doesn't properly clean up loading state in all paths.

 } catch (err) {
-  console.log("error creating Xmtp client", err);
+  console.error("Error creating Xmtp client:", err);
   setStatusString("");
   setError(
-    "Error creating Xmtp client : " +
-      (err instanceof Error ? err.message : "") +
-      (typeof err === "string" ? err : "")
+    `Error creating Xmtp client: ${
+      err instanceof Error ? err.message : 
+      typeof err === "string" ? err : 
+      "Unknown error"
+    }`
   );
 }
📝 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
const createXmtpClientFromAccount = useCallback(async () => {
try {
setLoading(true);
if (!account) {
setStatusString("Need to set an account first");
return;
}
await initXmtpClientFromViemAccount({
account,
address: account.address,
});
setStatusString("Xmtp client created");
if (isMissingConverseProfile()) {
router.navigate("NewAccountUserProfile");
} else {
router.navigate("Chats");
}
} catch (err) {
console.log("error creating Xmtp client", err);
setStatusString("");
setError(
"Error creating Xmtp client : " +
(err instanceof Error ? err.message : "") +
(typeof err === "string" ? err : "")
);
} finally {
setLoading(false);
}
}, [account, router, setError, setLoading, setStatusString]);
const createXmtpClientFromAccount = useCallback(async () => {
try {
setLoading(true);
if (!account) {
setStatusString("Need to set an account first");
return;
}
await initXmtpClientFromViemAccount({
account,
address: account.address,
});
setStatusString("Xmtp client created");
if (isMissingConverseProfile()) {
router.navigate("NewAccountUserProfile");
} else {
router.navigate("Chats");
}
} catch (err) {
console.error("Error creating Xmtp client:", err);
setStatusString("");
setError(
`Error creating Xmtp client: ${
err instanceof Error ? err.message :
typeof err === "string" ? err :
"Unknown error"
}`
);
} finally {
setLoading(false);
}
}, [account, router, setError, setLoading, setStatusString]);

Comment on lines 147 to 177
const createXmtpClientFromAccount = useCallback(async () => {
try {
setLoading(true);
if (!account) {
setStatusString("Need to set an account first");
return;
}
await initXmtpClientFromViemAccount({
account,
address: account.address,
});
if (isMissingConverseProfile()) {
router.navigate("OnboardingUserProfile");
} else if (needToShowNotificationsPermissions()) {
router.navigate("OnboardingNotifications");
} else {
setAuthStatus("signedIn");
}
setStatusString("Xmtp client created");
} catch (err) {
console.log("error creating Xmtp client", err);
setStatusString("");
setError(
"Error creating Xmtp client : " +
(err instanceof Error ? err.message : "") +
(typeof err === "string" ? err : "")
);
} finally {
setLoading(false);
}
}, [account, router, setError, setLoading, setStatusString]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling in createXmtpClientFromAccount.

The error handling could be improved:

  1. Console.log should use logger utility for consistency
  2. Error message concatenation could be simplified
  3. Status string is cleared on error which might confuse users
 } catch (err) {
-  console.log("error creating Xmtp client", err);
+  logger.error("Error creating Xmtp client:", err);
   setStatusString("");
   setError(
-    "Error creating Xmtp client : " +
-      (err instanceof Error ? err.message : "") +
-      (typeof err === "string" ? err : "")
+    `Error creating Xmtp client: ${
+      err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error"
+    }`
   );
 }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 71 to 97
const handleCreatePasskey = useCallback(async () => {
try {
setLoading(true);
const account = await onPasskeyCreate({
passkeyName: inputTextRef.current ?? "",
setStatusString,
setPreviousPasskeyName,
setTurnkeyInfo,
});
if (!account) {
setError("No account created from Passkey");
return;
}
setAccount(account);
} catch (e) {
setError(e instanceof Error ? e.message : "Unknown error");
} finally {
setLoading(false);
}
}, [
setAccount,
setError,
setLoading,
setPreviousPasskeyName,
setStatusString,
setTurnkeyInfo,
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Prevent multiple simultaneous operations.

The loading state is shared across all operations which could lead to race conditions.

Consider using operation-specific loading states:

+const [createLoading, setCreateLoading] = useState(false);
+const [loginLoading, setLoginLoading] = useState(false);
 const handleCreatePasskey = useCallback(async () => {
   try {
-    setLoading(true);
+    setCreateLoading(true);
     // ... rest of the code
   } finally {
-    setLoading(false);
+    setCreateLoading(false);
   }
 }, []);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +55 to +60
passkey: {
title: "Create Passkey",
add_account_title: "Add account by passkey",
subtitle: "Create a passkey to connect to Converse",
createButton: "Create Passkey",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add missing error messages for passkey operations.

The passkey translations should include error messages for common failure scenarios.

Add these translations:

 passkey: {
   title: "Create Passkey",
   add_account_title: "Add account by passkey",
   subtitle: "Create a passkey to connect to Converse",
   createButton: "Create Passkey",
+  errors: {
+    creation_failed: "Failed to create passkey",
+    login_failed: "Failed to login with passkey",
+    invalid_name: "Invalid passkey name",
+    already_exists: "A passkey with this name already exists",
+  },
 },
📝 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
passkey: {
title: "Create Passkey",
add_account_title: "Add account by passkey",
subtitle: "Create a passkey to connect to Converse",
createButton: "Create Passkey",
},
passkey: {
title: "Create Passkey",
add_account_title: "Add account by passkey",
subtitle: "Create a passkey to connect to Converse",
createButton: "Create Passkey",
errors: {
creation_failed: "Failed to create passkey",
login_failed: "Failed to login with passkey",
invalid_name: "Invalid passkey name",
already_exists: "A passkey with this name already exists",
},
},


async instantiateXmtpClient() {
if (!this.client) {
throw new Error("Client not found");
Copy link
Collaborator

Choose a reason for hiding this comment

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

This feels wrong if we’re extension in a client why are we throwing? Is the clients not found?
We shouldn’t expect a client if we’re in instantiating I think we just need to remove this bam or add one more

Copy link
Collaborator

Choose a reason for hiding this comment

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

This isn’t proof of concept related, but I do not like the pattern of unnecessarily putting exhaust stand into context. Not sure why we would do this.

Are there any benefits?

Updates to work with Navigation v7
Added nicer flow for users to 'test'

Created helpers
Cleaned up file names
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🔭 Outside diff range comments (2)
screens/NewAccount/NewAccountConnectWalletScreen.tsx (1)

Line range hint 26-28: Add missing dependency to useCallback

The empty dependency array causes isMissingConverseProfile result to be stale, which could lead to incorrect navigation decisions.

    },
-     // eslint-disable-next-line react-hooks/exhaustive-deps
-     []
+     [router]
    );
screens/NewAccount/NewAccountPrivateKeyScreen.tsx (1)

Line range hint 28-31: Add private key format validation

The current implementation only checks if the private key is empty. Consider adding proper format validation to prevent runtime errors.

    try {
      const trimmedPrivateKey = privateKey.trim();
      if (!trimmedPrivateKey) return;
+     // Private keys are typically 64 characters (32 bytes) in hex format
+     if (!/^[0-9a-fA-F]{64}$/.test(trimmedPrivateKey)) {
+       throw new Error('Invalid private key format');
+     }
      await loginWithPrivateKey(trimmedPrivateKey);
♻️ Duplicate comments (12)
utils/passkeys/create-suborganization.ts (1)

4-9: 🛠️ Refactor suggestion

Add error handling and improve type safety.

The function needs error handling and would benefit from explicit return type definition.

+type SubOrganizationResponse = {
+  subOrganizationId: string;
+  walletId: string;
+  address: string;
+};
+
 export async function createSubOrganization(
   authenticatorParams: Awaited<ReturnType<typeof createPasskey>>
-) {
-  const res = await createTurnkeySubOrganization(authenticatorParams);
+): Promise<SubOrganizationResponse> {
+  try {
+    const res = await createTurnkeySubOrganization(authenticatorParams);
+    return res;
+  } catch (error) {
+    console.error("Failed to create sub-organization", error);
+    throw error;
+  }
+}
utils/api.ts (1)

830-847: 🛠️ Refactor suggestion

Add error handling for wallet creation.

The function should handle potential errors during wallet creation and provide meaningful error messages.

 export const createTurnkeySubOrganization = async (
   authenticatorParams: TurnkeyAuthenticatorParams
 ): Promise<{
   subOrganizationId: string;
   walletId: string;
   address: string;
 }> => {
+  try {
     const { data } = await api.post(
       "/api/wallet/create-wallet-turnkey",
       {
         authenticatorParams,
       },
       {
         headers: await getAppCheckTokenHeaders(),
       }
     );
     return data;
+  } catch (error) {
+    logger.error("[API] Failed to create Turnkey wallet", error);
+    throw new Error(
+      error instanceof Error
+        ? error.message
+        : "Failed to create wallet. Please try again."
+    );
+  }
 };
utils/xmtpRN/signer.ts (1)

17-25: 🛠️ Refactor suggestion

Add isSCW parameter to maintain consistency with ethers implementation

The viemAccountToXmtpSigner function assumes all Viem LocalAccounts are Smart Contract Wallets (SCW), which is inconsistent with the ethersSignerToXmtpSigner implementation that supports both SCW and EOA wallet types through an isSCW parameter.

Apply this diff to maintain consistency:

-export const viemAccountToXmtpSigner = (
-  account: LocalAccount<any>
-): XmtpSigner => ({
+export const viemAccountToXmtpSigner = (
+  account: LocalAccount<any>,
+  isSCW?: boolean
+): XmtpSigner => ({
   getAddress: () => Promise.resolve(account.address),
   getChainId: () => ethereum.id,
   getBlockNumber: () => undefined,
-  walletType: () => "SCW",
+  walletType: () => (isSCW ? "SCW" : "EOA"),
   signMessage: (message: string) => account.signMessage({ message }),
 });
components/Onboarding/init-xmtp-client.ts (1)

56-87: 🛠️ Refactor suggestion

Reduce code duplication and improve error handling

The initXmtpClientFromViemAccount function largely duplicates initXmtpClient. Consider refactoring to reduce duplication and improve error handling.

utils/passkeys/add-wallet-to-passkey.ts (1)

13-25: 🛠️ Refactor suggestion

Move API URL to environment configuration

The hardcoded API URL should be moved to environment configuration.

Add input validation for subOrgId

The function should validate the subOrgId parameter before proceeding with the wallet creation.

 export const addWalletToPasskey = async ({
   subOrgId,
   setStatusString,
 }: AddWalletToPasskeyParams) => {
+  if (!subOrgId?.trim()) {
+    throw new Error("Invalid subOrgId provided");
+  }
   setStatusString("Adding wallet to passkey. Creating Passkey Stamper...");
utils/passkeys/load-client-from-passkey.ts (3)

21-25: ⚠️ Potential issue

Critical: Replace hardcoded challenge with secure random value.

The hardcoded "TODO" challenge compromises the security of the passkey authentication process. A static challenge makes the system vulnerable to replay attacks.


27-30: ⚠️ Potential issue

Add input validation for passkey response.

The passkey response is used without validation, which could lead to security vulnerabilities.


44-47: 🛠️ Refactor suggestion

Enhance error handling to prevent information leakage.

The current error handling logs the entire error object, which might expose sensitive information.

screens/Onboarding/OnboardingPasskeyScreen.tsx (3)

71-97: 🛠️ Refactor suggestion

Prevent multiple simultaneous operations.

The loading state is shared across all operations which could lead to race conditions.


147-177: 🛠️ Refactor suggestion

Improve error handling in createXmtpClientFromAccount.

The error handling could be improved for better user experience and security.


248-253: ⚠️ Potential issue

Add input validation for passkey name.

The passkey name input lacks validation which could lead to security issues.

i18n/translations/en.ts (1)

55-60: ⚠️ Potential issue

Add missing error messages for passkey operations.

The passkey translations should include error messages for common failure scenarios.

Add these translations:

 passkey: {
   title: "Create Passkey",
   add_account_title: "Add account by passkey",
   subtitle: "Create a passkey to connect to Converse",
   createButton: "Create Passkey",
+  errors: {
+    creation_failed: "Failed to create passkey",
+    login_failed: "Failed to login with passkey",
+    invalid_name: "Invalid passkey name",
+    already_exists: "A passkey with this name already exists",
+  },
 },
🧹 Nitpick comments (9)
utils/passkeys/create-suborganization.ts (1)

4-9: Add JSDoc documentation.

Add documentation to explain the purpose of the function and its parameters.

+/**
+ * Creates a new sub-organization using Turnkey's passkey authentication.
+ * @param authenticatorParams - Parameters from the passkey creation process
+ * @returns Promise resolving to the created sub-organization details
+ * @throws Error if sub-organization creation fails
+ */
 export async function createSubOrganization(
utils/api.ts (1)

273-281: Enhance error message for App Check Token.

The error message should provide more context about why the token might be unavailable and potential remediation steps.

   if (!appCheckToken) {
-    throw new Error("No App Check Token Available");
+    throw new Error(
+      "No App Check Token Available. This may indicate an issue with Firebase App Check initialization or device integrity verification."
+    );
   }
screens/NewAccount/NewAccountConnectWalletScreen.tsx (1)

Line range hint 30-35: Add error tracking in error handler

Consider tracking errors consistently across screens using sentryTrackError.

    const handleErrorConnecting = useCallback(
      (arg: { error: Error }) => {
+       sentryTrackError(arg.error);
        router.goBack();
      },
-     // eslint-disable-next-line react-hooks/exhaustive-deps
-     []
+     [router]
    );
screens/NewAccount/NewAccountUserProfileScreen.tsx (1)

Line range hint 23-23: Navigation pattern improvement across onboarding flow

The consistent change from navigate to popTo across all screens is a good architectural decision that:

  1. Prevents navigation stack buildup during onboarding
  2. Ensures clean navigation state after completing onboarding
  3. Improves memory management by clearing unnecessary screens from the stack

This change aligns well with the PR objective of moving functionality to the backend, as it helps maintain a clean frontend state.

Also applies to: 36-36, 34-34, 45-45

config.ts (1)

79-79: Consider consolidating domain configurations.

The newly added appDomain values match the existing websiteDomain values in each environment. Consider either:

  1. Using websiteDomain instead of introducing a new property, or
  2. Document the specific purpose of appDomain vs websiteDomain if they serve different functions.

Also applies to: 99-99, 134-134

features/embedded-wallets/embedded-wallet.interface.ts (1)

6-16: Consider adding wallet status methods

For better integration with passkey functionality, consider adding methods to check wallet and passkey status:

  1. isInitialized(): boolean - Check if wallet is properly initialized
  2. hasPasskey(): Promise<boolean> - Check if passkey exists for this wallet
  3. getPasskeyStatus(): Promise<PasskeyStatus> - Get detailed passkey status

This would help with proper state management and user flow control in the UI.

utils/passkeys/add-wallet-to-passkey.ts (1)

27-42: Extract wallet creation parameters to constants

The wallet creation parameters contain several magic strings that should be extracted to named constants for better maintainability and reusability.

+const WALLET_PARAMS = {
+  CURVE: "CURVE_SECP256K1",
+  PATH_FORMAT: "PATH_FORMAT_BIP32",
+  DEFAULT_PATH: "m/44'/60'/0'/0/0",
+  ADDRESS_FORMAT: "ADDRESS_FORMAT_ETHEREUM"
+} as const;
+
 const res = await client.createWallet({
   type: "ACTIVITY_TYPE_CREATE_WALLET",
   timestampMs: Date.now().toString(),
   organizationId: subOrgId,
   parameters: {
     walletName: "Wallet " + uuidv4(),
     accounts: [
       {
-        curve: "CURVE_SECP256K1",
-        pathFormat: "PATH_FORMAT_BIP32",
-        path: "m/44'/60'/0'/0/0",
-        addressFormat: "ADDRESS_FORMAT_ETHEREUM",
+        curve: WALLET_PARAMS.CURVE,
+        pathFormat: WALLET_PARAMS.PATH_FORMAT,
+        path: WALLET_PARAMS.DEFAULT_PATH,
+        addressFormat: WALLET_PARAMS.ADDRESS_FORMAT,
       },
     ],
   },
 });
features/onboarding/passkey/passkeyAuthStore.tsx (1)

46-68: Consider adding state persistence.

The store implementation could benefit from state persistence to handle page reloads and app restarts gracefully.

Consider using Zustand's persist middleware:

+import { persist } from 'zustand/middleware'
 const createPasskeyAuthStore = (initProps: IPasskeyAuthStoreProps) => {
   const DEFAULT_PROPS: IPasskeyAuthStoreProps = {
     // ...
   };
-  return createStore<IPasskeyAuthStoreState>()((set) => ({
+  return createStore<IPasskeyAuthStoreState>()(
+    persist(
+      (set) => ({
         // ... existing store code
-      }));
+      }),
+      {
+        name: 'passkey-auth-storage',
+        partialize: (state) => ({
+          previousPasskeyName: state.previousPasskeyName,
+          turnkeyInfo: state.turnkeyInfo,
+        }),
+      }
+    )
+  );
 };
screens/Onboarding/OnboardingPasskeyScreen.tsx (1)

179-211: Extract onboarding logic to a separate hook.

The onboardWithPasskey function combines multiple responsibilities. Consider extracting it to a custom hook for better reusability and testing.

Create a new hook:

function usePasskeyOnboarding() {
  // ... extract state and handlers
  const onboardWithPasskey = useCallback(async (passkeyName: string) => {
    // ... existing logic
  }, [/* dependencies */]);
  
  return {
    onboardWithPasskey,
    // ... other handlers
  };
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e09bf3d and c17fe74.

⛔ Files ignored due to path filters (2)
  • ios/Podfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (39)
  • app.json (1 hunks)
  • components/Avatar.tsx (1 hunks)
  • components/Onboarding/ConnectViaWallet/ConnectViaWalletTableViewItems.tsx (1 hunks)
  • components/Onboarding/init-xmtp-client.ts (2 hunks)
  • config.ts (3 hunks)
  • features/conversation/conversation.nav.tsx (1 hunks)
  • features/embedded-wallets/embedded-wallet.interface.ts (1 hunks)
  • features/embedded-wallets/ephemeral-embedded-wallet.ts (1 hunks)
  • features/embedded-wallets/turnkey-embedded-wallet.ts (1 hunks)
  • features/onboarding/passkey/passkeyAuthStore.tsx (1 hunks)
  • i18n/translations/en.ts (2 hunks)
  • ios/Converse/Converse.entitlements (1 hunks)
  • navigation/OnboardingNavigator.tsx (3 hunks)
  • package.json (2 hunks)
  • screens/Main.tsx (1 hunks)
  • screens/Navigation/Navigation.tsx (7 hunks)
  • screens/NewAccount/NewAccountConnectWalletScreen.tsx (1 hunks)
  • screens/NewAccount/NewAccountEphemeralScreen.tsx (2 hunks)
  • screens/NewAccount/NewAccountPasskeyScreen.tsx (1 hunks)
  • screens/NewAccount/NewAccountPrivateKeyScreen.tsx (1 hunks)
  • screens/NewAccount/NewAccountPrivyScreen.tsx (1 hunks)
  • screens/NewAccount/NewAccountScreen.tsx (2 hunks)
  • screens/NewAccount/NewAccountUserProfileScreen.tsx (1 hunks)
  • screens/Onboarding/OnboardingEphemeralScreen.tsx (1 hunks)
  • screens/Onboarding/OnboardingGetStartedScreen.tsx (2 hunks)
  • screens/Onboarding/OnboardingPasskeyScreen.tsx (1 hunks)
  • utils/api.ts (3 hunks)
  • utils/navigation.ts (1 hunks)
  • utils/passkeys/add-wallet-to-passkey.ts (1 hunks)
  • utils/passkeys/create-passkey.ts (1 hunks)
  • utils/passkeys/create-suborganization.ts (1 hunks)
  • utils/passkeys/create-turnkey-account.ts (1 hunks)
  • utils/passkeys/load-client-from-passkey.ts (1 hunks)
  • utils/passkeys/passkeys.constants.ts (1 hunks)
  • utils/passkeys/passkeys.interfaces.ts (1 hunks)
  • utils/passkeys/persist-passkeys.ts (1 hunks)
  • utils/passkeys/turnkey-clients.ts (1 hunks)
  • utils/xmtpRN/signIn.ts (2 hunks)
  • utils/xmtpRN/signer.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (21)
  • features/conversation/conversation.nav.tsx
  • ios/Converse/Converse.entitlements
  • screens/Main.tsx
  • utils/passkeys/passkeys.constants.ts
  • screens/Onboarding/OnboardingGetStartedScreen.tsx
  • screens/NewAccount/NewAccountScreen.tsx
  • utils/passkeys/turnkey-clients.ts
  • utils/navigation.ts
  • screens/Onboarding/OnboardingEphemeralScreen.tsx
  • utils/passkeys/persist-passkeys.ts
  • components/Onboarding/ConnectViaWallet/ConnectViaWalletTableViewItems.tsx
  • package.json
  • utils/passkeys/create-turnkey-account.ts
  • features/embedded-wallets/turnkey-embedded-wallet.ts
  • features/embedded-wallets/ephemeral-embedded-wallet.ts
  • utils/passkeys/passkeys.interfaces.ts
  • screens/Navigation/Navigation.tsx
  • app.json
  • navigation/OnboardingNavigator.tsx
  • screens/NewAccount/NewAccountPasskeyScreen.tsx
  • utils/passkeys/create-passkey.ts
🔇 Additional comments (12)
screens/NewAccount/NewAccountConnectWalletScreen.tsx (1)

23-23: LGTM! Navigation behavior improvement

Using popTo instead of navigate is a better choice here as it prevents navigation stack buildup during the onboarding flow.

screens/NewAccount/NewAccountPrivyScreen.tsx (1)

36-36: LGTM! Consistent navigation pattern

The change to popTo aligns with the navigation pattern across screens and properly manages the navigation stack. Error handling is also properly implemented with error tracking.

screens/NewAccount/NewAccountPrivateKeyScreen.tsx (1)

34-34: LGTM! Consistent navigation pattern

The change to popTo aligns with the navigation pattern across screens.

screens/NewAccount/NewAccountUserProfileScreen.tsx (1)

45-45: LGTM! Clean implementation

The changes maintain consistency with the navigation pattern across screens. The implementation includes proper input validation, error handling, and dependency management.

Also applies to: 50-50

config.ts (2)

79-79: Ensure consistent domain usage across the application.

The domain values are used in multiple places:

  • Universal links configuration
  • Web credentials
  • Passkey authentication

Verify that these new domain configurations are consistently used across the application.

Also applies to: 99-99, 134-134

✅ Verification successful

Domain configuration is properly implemented and consistent

The domain values are consistently configured across all required areas:

  • Universal links configuration matches environment-specific domains
  • Web credentials use correct domain values per environment
  • Deep linking and intent filters are properly configured
  • Both primary (.converse.xyz) and alternative (.getconverse.app) domains are consistently handled
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for domain usage across the codebase
for domain in "dev.converse.xyz" "preview.converse.xyz" "converse.xyz"; do
  echo "Checking usage of $domain..."
  rg -l "$domain"
done

Length of output: 1050


Script:

#!/bin/bash
# Check universal links and authentication related configurations
echo "Checking universal links and authentication configurations..."
rg -A 3 "associatedDomains|universalLinks|webCredentials|passkey" config.ts app.config.ts

echo -e "\nChecking domain usage in config files..."
cat config.ts

echo -e "\nChecking app config..."
cat app.config.ts

Length of output: 12412


79-79: Verify domain configuration for passkey authentication.

Since these domains are used for passkey authentication, ensure:

  1. DNS records are properly configured
  2. SSL certificates are valid
  3. Associated entitlements are correctly set up in the iOS configuration

Also applies to: 99-99, 134-134

utils/passkeys/add-wallet-to-passkey.ts (2)

1-11: LGTM! Clean imports and well-structured type definition.

The imports are properly organized, and the AddWalletToPasskeyParams type clearly defines the expected parameters.


1-50: Verify security implications of wallet creation

Please ensure:

  1. The RPID constant is properly configured for the production environment
  2. The wallet creation parameters align with security best practices
  3. Error messages don't leak sensitive information
✅ Verification successful

Security implementation verified and follows best practices

The implementation is secure with:

  • Environment-aware RPID configuration
  • Industry-standard wallet creation parameters
  • Proper error handling without sensitive data exposure
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential security configurations
echo "Checking RPID configuration..."
rg -A 1 "RPID.*=.*" --glob '**/*.{ts,js}'

echo "Checking for potential sensitive data exposure in error messages..."
rg "throw.*Error.*" --glob '**/*.{ts,js}'

echo "Checking wallet creation parameters across the codebase..."
rg "CURVE_SECP256K1|PATH_FORMAT_BIP32|ADDRESS_FORMAT_ETHEREUM" --glob '**/*.{ts,js}'

Length of output: 7367


Script:

#!/bin/bash
# Check config.appDomain configuration
echo "Checking config.appDomain source..."
rg -p "config.*appDomain" --glob '**/*.{ts,js}'

echo "Checking for any config files..."
fd -e ts -e js -g "*config*"

Length of output: 426


Script:

#!/bin/bash
echo "Checking config.ts content..."
cat config.ts

echo -e "\nChecking for appDomain references..."
rg "appDomain" --glob '**/*.{ts,js}'

Length of output: 5737

components/Avatar.tsx (1)

50-50: Verify the impact of style changes.

The removal of styles.imageContainer from the View component might affect the layout. Please ensure that all essential styles are properly passed via the style prop.

✅ Verification successful

Style changes are safe to proceed

The removal of styles.imageContainer doesn't impact the component's functionality as all essential image styling is handled internally, while the style prop is correctly used for external positioning. The component is well-tested and widely used across the codebase with no dependencies on the removed style.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of Avatar component to verify style prop usage
rg -A 5 '<Avatar[^>]*style=' 

Length of output: 30


Script:

#!/bin/bash
# Search for any Avatar component usage
rg '<Avatar' -A 5

# Examine the Avatar component implementation
cat components/Avatar.tsx

Length of output: 19270

i18n/translations/en.ts (1)

11-11: LGTM!

The new translation key follows consistent naming patterns and provides clear messaging.

screens/NewAccount/NewAccountEphemeralScreen.tsx (2)

Line range hint 28-46: Well-implemented wallet generation and error handling

The implementation demonstrates good practices:

  • Secure random key generation
  • Proper error handling with Sentry tracking
  • Clear loading state management
  • Clean React hooks usage

40-40: Verify navigation behavior change

The navigation method has been changed from navigate to popTo. This changes how the screen stack is managed and affects the back button behavior.

Please confirm:

  1. This change is intentional and part of a broader navigation pattern update
  2. Users can still navigate back if needed
  3. No screens are accidentally removed from the stack
✅ Verification successful

Navigation pattern change is consistent with codebase

The change from navigate to popTo is part of a consistent pattern where account creation flows use popTo("Chats") to clean up the navigation stack after completing multi-step processes. This is the correct approach as it prevents stack buildup from intermediate screens.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for consistent navigation pattern across related files
echo "Checking navigation patterns in related files..."
rg --type typescript -B 2 -A 2 'router\.(navigate|popTo)\("Chats"\)' ./screens/NewAccount/

Length of output: 234


Script:

#!/bin/bash
# Search for navigation patterns to "Chats" screen across the codebase
echo "Searching for navigation patterns..."
rg -t tsx -t ts -B 2 -A 2 'router\.(navigate|popTo)\("Chats"\)'

# Also check for any other references to the Chats screen in navigation
echo -e "\nChecking other navigation references to Chats screen..."
rg -t tsx -t ts 'Chats"?\s*[,)]' --context 2

Length of output: 378


Script:

#!/bin/bash
# Search for navigation patterns using find and grep
echo "Searching for navigation patterns in TypeScript files..."
find . -type f \( -name "*.ts" -o -name "*.tsx" \) -exec grep -l -A 2 -B 2 'router\.\(navigate\|popTo\)("Chats")' {} \;

# Look for screen definitions or routes related to Chats
echo -e "\nChecking screen definitions and routes..."
find . -type f \( -name "*.ts" -o -name "*.tsx" \) -exec grep -l "screen.*[\"']Chats[\"']" {} \;

Length of output: 781


Script:

#!/bin/bash
# Check the actual navigation methods used in the identified files
echo "Checking navigation methods in the identified files..."
for file in ./components/AccountSettingsButton.tsx ./screens/Accounts/Accounts.tsx ./screens/NewAccount/*.tsx; do
    echo -e "\n=== $file ==="
    grep -A 1 -B 1 'router\.' "$file" || true
done

Length of output: 5081

Comment on lines +72 to +120
export const createXmtpClientFromViemAccount = async (
account: LocalAccount,
onInstallationRevoked: () => Promise<void>,
preAuthenticateToInboxCallback?: () => Promise<void>
) => {
const tempDirectory = await createTemporaryDirectory();
const dbEncryptionKey = await getDbEncryptionKey();

const options = {
env,
enableV3: true,
dbDirectory: tempDirectory,
dbEncryptionKey,
};
const inboxId = await getInboxId(await account.address);

await copyDatabasesToTemporaryDirectory(tempDirectory, inboxId);

logger.debug("Instantiating client from signer");

const client = await Client.create(viemAccountToXmtpSigner(account), {
...options,
preAuthenticateToInboxCallback,
});

if (client.inboxId !== inboxId) {
throw new Error("Inbox ids don't match");
}

// In case we're logging with an existing libxmtp database, make sure
// the installation has not already been revoked
const installationValid = await isClientInstallationValid(client);
if (!installationValid) {
await client.dropLocalDatabaseConnection();
await deleteLibXmtpDatabaseForInboxId(inboxId);
onInstallationRevoked();
return;
}

logger.debug("Instantiated client from signer");
// This Client is only be used to extract the key, we can disconnect
// it to prevent locks happening during Onboarding
await client.dropLocalDatabaseConnection();
await moveTemporaryDatabasesToDatabaseDirectory(
tempDirectory,
client.inboxId
);
logger.debug("Dropped client databases");
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reduce code duplication with createXmtpClientFromSigner

The new function duplicates most of the logic from createXmtpClientFromSigner. Consider extracting the common functionality into a shared helper function.

Apply this diff to reduce duplication:

+async function createXmtpClientBase<T>(
+  signerOrAccount: T,
+  createSigner: (input: T) => XmtpSigner,
+  onInstallationRevoked: () => Promise<void>,
+  preAuthenticateToInboxCallback?: () => Promise<void>
+) {
+  const tempDirectory = await createTemporaryDirectory();
+  const dbEncryptionKey = await getDbEncryptionKey();
+
+  const options = {
+    env,
+    enableV3: true,
+    dbDirectory: tempDirectory,
+    dbEncryptionKey,
+  };
+
+  const address = typeof signerOrAccount === 'object' && 'address' in signerOrAccount
+    ? signerOrAccount.address
+    : await (signerOrAccount as Signer).getAddress();
+    
+  const inboxId = await getInboxId(address);
+
+  await copyDatabasesToTemporaryDirectory(tempDirectory, inboxId);
+
+  logger.debug("Instantiating client from signer");
+
+  const client = await Client.create(createSigner(signerOrAccount), {
+    ...options,
+    preAuthenticateToInboxCallback,
+  });
+
+  if (client.inboxId !== inboxId) {
+    throw new Error("Inbox ids don't match");
+  }
+
+  const installationValid = await isClientInstallationValid(client);
+  if (!installationValid) {
+    await client.dropLocalDatabaseConnection();
+    await deleteLibXmtpDatabaseForInboxId(inboxId);
+    onInstallationRevoked();
+    return;
+  }
+
+  logger.debug("Instantiated client from signer");
+  await client.dropLocalDatabaseConnection();
+  await moveTemporaryDatabasesToDatabaseDirectory(tempDirectory, client.inboxId);
+  logger.debug("Dropped client databases");
+}

 export const createXmtpClientFromSigner = async (
   signer: Signer,
   onInstallationRevoked: () => Promise<void>,
   preAuthenticateToInboxCallback?: () => Promise<void>
-) => {
-  const tempDirectory = await createTemporaryDirectory();
-  // ... rest of the implementation
+) => createXmtpClientBase(
+  signer,
+  (s) => ethersSignerToXmtpSigner(s),
+  onInstallationRevoked,
+  preAuthenticateToInboxCallback
+);

 export const createXmtpClientFromViemAccount = async (
   account: LocalAccount,
   onInstallationRevoked: () => Promise<void>,
   preAuthenticateToInboxCallback?: () => Promise<void>
-) => {
-  const tempDirectory = await createTemporaryDirectory();
-  // ... rest of the implementation
+) => createXmtpClientBase(
+  account,
+  (a) => viemAccountToXmtpSigner(a),
+  onInstallationRevoked,
+  preAuthenticateToInboxCallback
+);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +65 to +67
if (!account || !address) {
throw new Error("No signer or address");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect error message

The error message refers to "signer" but the function checks for "account".

Apply this diff to fix the error message:

  if (!account || !address) {
-   throw new Error("No signer or address");
+   throw new Error("No account or address");
  }
📝 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 (!account || !address) {
throw new Error("No signer or address");
}
if (!account || !address) {
throw new Error("No account or address");
}

Comment on lines +6 to +16
export type IEmbeddedWallet = {
account: Account | null;

createAccount: () => Promise<Account>;

loadAccount: () => Promise<Account>;

xmtpClient: Client | null;

instantiateXmtpClient: () => Promise<Client>;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add documentation and error handling patterns

The interface would benefit from the following improvements:

  1. Add JSDoc comments describing the purpose and requirements of each method
  2. Define error types/patterns for async operations
  3. Add a cleanup method for proper XMTP client resource management

Here's a suggested implementation:

+/** Represents an embedded wallet with account and XMTP messaging capabilities */
 export type IEmbeddedWallet = {
   account: Account | null;
 
+  /**
+   * Creates a new wallet account
+   * @throws {WalletCreationError} If account creation fails
+   * @returns Promise resolving to the created account
+   */
   createAccount: () => Promise<Account>;
 
+  /**
+   * Loads an existing wallet account
+   * @throws {WalletLoadError} If account loading fails
+   * @returns Promise resolving to the loaded account
+   */
   loadAccount: () => Promise<Account>;
 
   xmtpClient: Client | null;
 
+  /**
+   * Instantiates an XMTP client for messaging
+   * @throws {XmtpError} If client instantiation fails
+   * @returns Promise resolving to the XMTP client
+   */
   instantiateXmtpClient: () => Promise<Client>;
+
+  /**
+   * Cleanup resources and disconnect clients
+   * @returns Promise resolving when cleanup is complete
+   */
+  cleanup: () => Promise<void>;
 };

Additionally, consider creating custom error types for better error handling:

export class WalletCreationError extends Error {
  constructor(message: string, public readonly cause?: unknown) {
    super(message);
    this.name = 'WalletCreationError';
  }
}

// Similar error classes for WalletLoadError and XmtpError

Comment on lines +1 to +4
import { Client } from "@xmtp/react-native-sdk";
import { LocalAccount } from "viem/accounts";

type Account = LocalAccount<any>;
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Use LocalAccount<"privateKey"> instead of LocalAccount<any>

The LocalAccount<"privateKey"> type is used in the concrete implementation (EphemeralEmbeddedWallet), which aligns with the account creation flow using privateKeyToAccount. Using any reduces type safety and doesn't accurately represent the expected type parameter.

🔗 Analysis chain

Consider specifying the type parameter for LocalAccount

The any type parameter in LocalAccount<any> reduces type safety. Consider specifying the exact type parameter expected by the LocalAccount generic type to ensure better type checking and documentation.

Let's check the viem documentation and usage:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for LocalAccount usage patterns in the codebase
rg "LocalAccount<" --type ts

Length of output: 279


Script:

#!/bin/bash
# Check the implementation details of ephemeral wallet
cat features/embedded-wallets/ephemeral-embedded-wallet.ts

Length of output: 1636


Script:

#!/bin/bash
# Search for account-related imports from viem
rg "from 'viem/accounts'" --type ts -A 2

Length of output: 48


Script:

#!/bin/bash
# Search for viem imports with double quotes and broader pattern
rg 'from "viem/' --type ts -A 2

# Also search for privateKeyToAccount usage as it's relevant
rg "privateKeyToAccount" --type ts

Length of output: 1995

Comment on lines +44 to +50
const address = res.activity.result.createWalletResult?.addresses[0];
if (!address) {
throw new Error("No address found");
}
const account = await createTurnkeyAccount(address, subOrgId);
return { address, account };
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and add retry mechanism

The current error handling could expose sensitive information and lacks retry capability for transient failures.

+const MAX_RETRIES = 3;
+const RETRY_DELAY = 1000; // ms
+
+const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
+
+async function withRetry<T>(
+  operation: () => Promise<T>,
+  retries: number = MAX_RETRIES
+): Promise<T> {
+  try {
+    return await operation();
+  } catch (error) {
+    if (retries > 0) {
+      await sleep(RETRY_DELAY);
+      return withRetry(operation, retries - 1);
+    }
+    throw new Error("Operation failed after multiple retries");
+  }
+}
+
-  const address = res.activity.result.createWalletResult?.addresses[0];
+  const address = res.activity.result.createWalletResult?.addresses?.[0];
   if (!address) {
-    throw new Error("No address found");
+    throw new Error("Wallet creation failed: address not generated");
   }
-  const account = await createTurnkeyAccount(address, subOrgId);
+  const account = await withRetry(() => createTurnkeyAccount(address, subOrgId));
   return { address, account };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +55 to +67
return createStore<IPasskeyAuthStoreState>()((set) => ({
...DEFAULT_PROPS,
...initProps,
setLoading: (loading) =>
loading ? set({ loading, error: undefined }) : set({ loading: false }),
setError: (error) => set({ error, statusString: undefined }),
setStatusString: (statusString) => set({ statusString }),
setAccount: (account) => set({ account }),
setTurnkeyInfo: (turnkeyInfo) => set({ turnkeyInfo }),
setPreviousPasskeyName: (previousPasskeyName) =>
set({ previousPasskeyName }),
reset: () => set(DEFAULT_PROPS),
}));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add state update validation.

The state setters don't validate input, which could lead to inconsistent state.

Add validation to prevent invalid state updates:

 setTurnkeyInfo: (turnkeyInfo) => 
-  set({ turnkeyInfo }),
+  set((state) => {
+    if (turnkeyInfo && (!turnkeyInfo.subOrganizationId || !turnkeyInfo.walletId)) {
+      console.warn('Invalid turnkey info provided');
+      return state;
+    }
+    return { turnkeyInfo };
+  }),
📝 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
return createStore<IPasskeyAuthStoreState>()((set) => ({
...DEFAULT_PROPS,
...initProps,
setLoading: (loading) =>
loading ? set({ loading, error: undefined }) : set({ loading: false }),
setError: (error) => set({ error, statusString: undefined }),
setStatusString: (statusString) => set({ statusString }),
setAccount: (account) => set({ account }),
setTurnkeyInfo: (turnkeyInfo) => set({ turnkeyInfo }),
setPreviousPasskeyName: (previousPasskeyName) =>
set({ previousPasskeyName }),
reset: () => set(DEFAULT_PROPS),
}));
return createStore<IPasskeyAuthStoreState>()((set) => ({
...DEFAULT_PROPS,
...initProps,
setLoading: (loading) =>
loading ? set({ loading, error: undefined }) : set({ loading: false }),
setError: (error) => set({ error, statusString: undefined }),
setStatusString: (statusString) => set({ statusString }),
setAccount: (account) => set({ account }),
setTurnkeyInfo: (turnkeyInfo) =>
set((state) => {
if (turnkeyInfo && (!turnkeyInfo.subOrganizationId || !turnkeyInfo.walletId)) {
console.warn('Invalid turnkey info provided');
return state;
}
return { turnkeyInfo };
}),
setPreviousPasskeyName: (previousPasskeyName) =>
set({ previousPasskeyName }),
reset: () => set(DEFAULT_PROPS),
}));

Comment on lines +21 to 22
export function NewAccountEphemeralScreen(
props: NativeStackScreenProps<NavigationParamList, "NewAccountEphemera">
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix type definition to match component name

The route parameter in NativeStackScreenProps still uses "NewAccountEphemera" while the component has been renamed to NewAccountEphemeralScreen. This mismatch could cause type errors.

Update the type definition to match the new name:

export function NewAccountEphemeralScreen(
-  props: NativeStackScreenProps<NavigationParamList, "NewAccountEphemera">
+  props: NativeStackScreenProps<NavigationParamList, "NewAccountEphemeral">
📝 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
export function NewAccountEphemeralScreen(
props: NativeStackScreenProps<NavigationParamList, "NewAccountEphemera">
export function NewAccountEphemeralScreen(
props: NativeStackScreenProps<NavigationParamList, "NewAccountEphemeral">

Added better support for login with passkeys
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (9)
utils/passkeys/create-passkey.ts (3)

53-56: 🛠️ Refactor suggestion

Enhance error handling with specific error messages

The current error handling only logs and rethrows errors without providing user-friendly feedback.

   } catch (e) {
     console.error("error during passkey creation", e);
+    setStatusString(`Failed to create passkey: ${e instanceof Error ? e.message : 'Unknown error'}`);
     throw e;
   }

59-62: ⚠️ Potential issue

Add early return for unsupported devices

The function continues execution even when passkeys are not supported.

   if (!isSupported()) {
     alert("Passkeys are not supported on this device");
+    return Promise.reject(new Error("Passkeys not supported"));
   }

67-88: ⚠️ Potential issue

Add error handling for createPasskey operation

The createPasskey call lacks error handling.

-  const authenticatorParams = await createPasskey({
+  let authenticatorParams;
+  try {
+    authenticatorParams = await createPasskey({
       authenticatorName: "End-User Passkey",
       rp: {
         id: RPID,
         name: "Passkey App",
       },
       user: {
         id: userId,
         name: passkeyName + " " + humanReadableDate,
         displayName: passkeyName + " " + humanReadableDate,
       },
       authenticatorSelection: {
         residentKey: "required",
         requireResidentKey: true,
         userVerification: "preferred",
       },
-    });
+    });
+  } catch (error) {
+    console.error("Failed to create passkey:", error);
+    throw new Error(
+      `Passkey creation failed: ${error instanceof Error ? error.message : 'Unknown error'}`
+    );
+  }
utils/passkeys/turnkey-clients.ts (2)

7-9: ⚠️ Potential issue

Add error handling for missing clients

The getter should handle the case when a client doesn't exist for the given subOrgId.

 export const getTurnkeyClient = (subOrgId: string) => {
+  if (!turnkeyClientMap[subOrgId]) {
+    throw new Error(`No TurnkeyClient found for subOrgId: ${subOrgId}`);
+  }
   return turnkeyClientMap[subOrgId];
 };

11-16: ⚠️ Potential issue

Add input validation for setTurnkeyClient

The setter should validate its inputs to prevent potential issues.

 export const setTurnkeyClient = (
   subOrgId: string,
   turnkeyClient: TurnkeyClient
 ) => {
+  if (!subOrgId?.trim()) {
+    throw new Error('subOrgId is required');
+  }
+  if (!turnkeyClient) {
+    throw new Error('turnkeyClient is required');
+  }
   turnkeyClientMap[subOrgId] = turnkeyClient;
 };
utils/passkeys/load-client-from-passkey.ts (2)

26-28: 🛠️ Refactor suggestion

Move API URL to environment configuration

Hardcoding the API URL is not a good practice. Move it to environment configuration.

     const client = new TurnkeyClient(
-      { baseUrl: "https://api.turnkey.com" },
+      { baseUrl: config.turnkeyApiUrl },
       stamper
     );

67-70: ⚠️ Potential issue

Enhance error handling to prevent information leakage

The current error handling logs the entire error object, which might expose sensitive information.

   } catch (e) {
-    console.error("error during passkey load", e);
-    throw e;
+    console.error("Error during passkey load:", e instanceof Error ? e.message : "Unknown error");
+    throw new Error("Failed to load passkey");
   }
screens/NewAccount/NewAccountPasskeyScreen.tsx (2)

167-174: ⚠️ Potential issue

Improve error handling in createXmtpClientFromAccount

The error handling concatenates different error types unsafely and uses console.log instead of console.error.

-      console.log("error creating Xmtp client", err);
+      console.error("Error creating Xmtp client:", err);
       setStatusString("");
-      handleError(
-        "Error creating Xmtp client : " +
-          (err instanceof Error ? err.message : "") +
-          (typeof err === "string" ? err : "")
-      );
+      handleError(
+        `Error creating Xmtp client: ${
+          err instanceof Error ? err.message : 
+          typeof err === "string" ? err : 
+          "Unknown error"
+        }`
+      );

284-289: ⚠️ Potential issue

Add input validation for passkey name

The TextField for passkey name lacks validation and error handling.

 <TextField
   placeholder="Enter your passkey"
+  error={inputTextRef.current?.length < 3 ? "Passkey name must be at least 3 characters" : undefined}
   onChangeText={(text) => {
     inputTextRef.current = text;
   }}
+  onBlur={() => {
+    if (!inputTextRef.current) {
+      setError("Passkey name is required");
+    }
+  }}
 />
🧹 Nitpick comments (6)
utils/passkeys/create-passkey.ts (2)

25-51: Improve status messages for better user experience

The status messages could be more user-friendly and informative.

-    setStatusString("Creating passkey...");
+    setStatusString("Setting up your secure passkey...");

-    setStatusString("Passkey creation successful. Creating sub-org...");
+    setStatusString("Passkey created successfully! Setting up your account...");

-    setStatusString("Sub-org created! Creating turnkey account...");
+    setStatusString("Account setup in progress...");

-    setStatusString(`Turnkey account created! Address is ${address}`);
+    setStatusString(`Setup complete! Your secure wallet address is ${address}`);

64-66: Improve user ID generation to prevent collisions

Using timestamp alone for ID generation could lead to collisions in high-concurrency scenarios.

-  const userId = Buffer.from(String(Date.now())).toString("base64");
+  const userId = Buffer.from(
+    `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
+  ).toString("base64");
utils/passkeys/turnkey-clients.ts (1)

20-22: Simplify boolean check

Remove redundant double negation.

-  if (!!client) {
+  if (client) {
     return client;
   }
🧰 Tools
🪛 Biome (1.9.4)

[error] 20-20: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

utils/passkeys/load-client-from-passkey.ts (2)

43-50: Improve embedded wallet detection

The current method of finding the embedded wallet could be improved.

-    // TODO: Is there a better way to find the embedded wallet?
-    const embeddedWallet = getWallets.wallets.find(
-      (wallet) => wallet.walletName === "Embedded Wallet"
-    );
+    const EMBEDDED_WALLET_NAME = "Embedded Wallet";
+    const embeddedWallet = getWallets.wallets.find(
+      (wallet) => wallet.walletName === EMBEDDED_WALLET_NAME
+    );
     if (!embeddedWallet) {
-      throw new Error("No embedded wallet found");
+      throw new Error(`No wallet found with name: ${EMBEDDED_WALLET_NAME}`);
     }

9-18: Remove unused parameter

The setPreviousPasskeyName parameter is defined but never used.

 type LoadAccountFromPasskeyParams = {
   setStatusString: (statusString: string) => void;
-  setPreviousPasskeyName: (passkeyName: string) => void;
   setTurnkeyInfo: (turnkeyInfo: TurnkeyStoreInfo) => void;
 };

 export const loadAccountFromPasskey = async ({
   setStatusString,
   setTurnkeyInfo,
 }: LoadAccountFromPasskeyParams) => {
screens/NewAccount/NewAccountPasskeyScreen.tsx (1)

1-20: Consider organizing imports by category

Consider grouping imports into categories for better maintainability:

  1. React/Framework imports
  2. Components
  3. Features/Utils
  4. Design system
 import React, { memo, useCallback, useRef } from "react";
+
 import { NewAccountScreenComp } from "@components/NewAccount/NewAccountScreenComp";
 import { NewAccountPictoTitleSubtitle } from "@components/NewAccount/NewAccountTitleSubtitlePicto";
+
 import {
   PasskeyAuthStoreProvider,
   usePasskeyAuthStoreContext,
 } from "@features/onboarding/passkey/passkeyAuthStore";
 import { translate } from "@i18n";
 import { useRouter } from "../../navigation/useNavigation";
 import { isMissingConverseProfile } from "../Onboarding/Onboarding.utils";
 import { addWalletToPasskey } from "@/utils/passkeys/add-wallet-to-passkey";
 import { initXmtpClientFromViemAccount } from "@/components/Onboarding/init-xmtp-client";
+
 import { Button } from "@/design-system/Button/Button";
 import { TextField } from "@/design-system/TextField/TextField";
 import { Text } from "@/design-system/Text";
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c17fe74 and de66f20.

📒 Files selected for processing (11)
  • App.tsx (0 hunks)
  • config.ts (4 hunks)
  • screens/Main.tsx (3 hunks)
  • screens/NewAccount/NewAccountPasskeyScreen.tsx (1 hunks)
  • screens/Onboarding/OnboardingPasskeyScreen.tsx (1 hunks)
  • utils/passkeys/create-passkey.ts (1 hunks)
  • utils/passkeys/create-turnkey-account.ts (1 hunks)
  • utils/passkeys/load-client-from-passkey.ts (1 hunks)
  • utils/passkeys/passkeys.constants.ts (1 hunks)
  • utils/passkeys/passkeys.interfaces.ts (1 hunks)
  • utils/passkeys/turnkey-clients.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • App.tsx
🚧 Files skipped from review as they are similar to previous changes (5)
  • utils/passkeys/passkeys.constants.ts
  • screens/Main.tsx
  • config.ts
  • utils/passkeys/passkeys.interfaces.ts
  • screens/Onboarding/OnboardingPasskeyScreen.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
utils/passkeys/turnkey-clients.ts

[error] 20-20: Avoid redundant double-negation.

It is not necessary to use double-negation when a value will already be coerced to a boolean.
Unsafe fix: Remove redundant double-negation

(lint/complexity/noExtraBooleanCast)

🔇 Additional comments (1)
utils/passkeys/create-passkey.ts (1)

11-16: Well-structured type definition!

The PasskeyCreateParams type clearly defines the required parameters with appropriate callback functions for status updates and state management.

Comment on lines +47 to +50
const turnkeyAccount = await createTurnkeyAccount(
address,
subOrganizationId!
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove non-null assertion operator

The non-null assertion operator (!) on subOrganizationId could lead to runtime errors if the value is undefined. Consider adding a validation check instead.

-      subOrganizationId!
+      subOrganizationId ?? throw new Error("SubOrganizationId is required")
📝 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
const turnkeyAccount = await createTurnkeyAccount(
address,
subOrganizationId!
);
const turnkeyAccount = await createTurnkeyAccount(
address,
subOrganizationId ?? throw new Error("SubOrganizationId is required")
);

rpId: RPID,
});

client = new TurnkeyClient({ baseUrl: "https://api.turnkey.com" }, stamper);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Move API URL to environment configuration

Hardcoding the API URL is not a good practice. Move it to environment configuration.

-  client = new TurnkeyClient({ baseUrl: "https://api.turnkey.com" }, stamper);
+  client = new TurnkeyClient({ baseUrl: config.turnkeyApiUrl }, stamper);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +31 to +49
export const createTurnkeyAccountFromWalletId = async (
walletId: string,
subOrganizationId: string
) => {
try {
const client = getOrCreateTurnkeyClient(subOrganizationId);

const account = await client.getWalletAccounts({
organizationId: subOrganizationId,
walletId,
});
const address = account.accounts[0].address;

return createTurnkeyAccount(address, subOrganizationId);
} catch (e) {
console.error("error during turnkey account creation", e);
throw e;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add JSDoc documentation and improve error handling

The function lacks documentation and has generic error handling.

+/**
+ * Creates a Turnkey account from a wallet ID.
+ * @param walletId - The ID of the wallet to create the account from.
+ * @param subOrganizationId - The sub-organization ID to create the account for.
+ * @returns The created Turnkey account.
+ * @throws {Error} If no accounts are found for the wallet.
+ */
 export const createTurnkeyAccountFromWalletId = async (
   walletId: string,
   subOrganizationId: string
 ) => {
   try {
     const client = getOrCreateTurnkeyClient(subOrganizationId);

     const account = await client.getWalletAccounts({
       organizationId: subOrganizationId,
       walletId,
     });
+    if (!account.accounts?.length) {
+      throw new Error('No accounts found for the wallet');
+    }
     const address = account.accounts[0].address;

     return createTurnkeyAccount(address, subOrganizationId);
   } catch (e) {
-    console.error("error during turnkey account creation", e);
-    throw e;
+    console.error("Error during turnkey account creation:", e instanceof Error ? e.message : "Unknown error");
+    throw new Error("Failed to create Turnkey account");
   }
 };
📝 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
export const createTurnkeyAccountFromWalletId = async (
walletId: string,
subOrganizationId: string
) => {
try {
const client = getOrCreateTurnkeyClient(subOrganizationId);
const account = await client.getWalletAccounts({
organizationId: subOrganizationId,
walletId,
});
const address = account.accounts[0].address;
return createTurnkeyAccount(address, subOrganizationId);
} catch (e) {
console.error("error during turnkey account creation", e);
throw e;
}
};
/**
* Creates a Turnkey account from a wallet ID.
* @param walletId - The ID of the wallet to create the account from.
* @param subOrganizationId - The sub-organization ID to create the account for.
* @returns The created Turnkey account.
* @throws {Error} If no accounts are found for the wallet.
*/
export const createTurnkeyAccountFromWalletId = async (
walletId: string,
subOrganizationId: string
) => {
try {
const client = getOrCreateTurnkeyClient(subOrganizationId);
const account = await client.getWalletAccounts({
organizationId: subOrganizationId,
walletId,
});
if (!account.accounts?.length) {
throw new Error('No accounts found for the wallet');
}
const address = account.accounts[0].address;
return createTurnkeyAccount(address, subOrganizationId);
} catch (e) {
console.error("Error during turnkey account creation:", e instanceof Error ? e.message : "Unknown error");
throw new Error("Failed to create Turnkey account");
}
};

Comment on lines +272 to +277
{turnkeyInfo && (
<Text preset="bodyBold">
Turnkey info:
<Text preset="body">{JSON.stringify(turnkeyInfo)}</Text>
</Text>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security: Remove sensitive information from UI

Displaying turnkey information in the UI could pose a security risk if screenshots are taken or if the screen is visible to others.

-      {turnkeyInfo && (
-        <Text preset="bodyBold">
-          Turnkey info:
-          <Text preset="body">{JSON.stringify(turnkeyInfo)}</Text>
-        </Text>
-      )}
+      {turnkeyInfo && (
+        <Text preset="bodyBold">
+          Passkey configured successfully
+        </Text>
+      )}
📝 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
{turnkeyInfo && (
<Text preset="bodyBold">
Turnkey info:
<Text preset="body">{JSON.stringify(turnkeyInfo)}</Text>
</Text>
)}
{turnkeyInfo && (
<Text preset="bodyBold">
Passkey configured successfully
</Text>
)}

Comment on lines 290 to 323
<Button
text={"Onboard with passkey"}
onPress={onboardWithPasskey}
loading={loading}
/>
<Button
text={"Add new wallet to existing passkey"}
onPress={addWalletToExistingPasskey}
loading={loading}
/>
<Button
text={translate("passkey.createButton")}
onPress={handleCreatePasskey}
loading={loading}
variant="secondary"
/>
<Button
text={"Login with passkey"}
onPress={handleLoginWithPasskey}
loading={loading}
variant="secondary"
/>
<Button
text={"Add new wallet to passkey"}
onPress={handleAddWalletToPasskey}
loading={loading}
variant="secondary"
/>
<Button
text={"Create Xmtp client from account"}
onPress={createXmtpClientFromAccount}
loading={loading}
variant="secondary"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve loading state management for multiple operations

Currently, all buttons share the same loading state, which could lead to unclear UI feedback when multiple operations are in progress. Consider using separate loading states for each operation.

+  const [createLoading, setCreateLoading] = useState(false);
+  const [loginLoading, setLoginLoading] = useState(false);
+  const [addWalletLoading, setAddWalletLoading] = useState(false);
+  const [xmtpLoading, setXmtpLoading] = useState(false);

   const handleCreatePasskey = useCallback(async () => {
     try {
-      setLoading(true);
+      setCreateLoading(true);
       // ... existing code ...
     } finally {
-      setLoading(false);
+      setCreateLoading(false);
     }
   }, [...]);

   // Update other handlers similarly

   return (
     // ... existing code ...
     <Button
       text={"Onboard with passkey"}
       onPress={onboardWithPasskey}
-      loading={loading}
+      loading={createLoading}
     />
     // Update other buttons similarly
   );

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (3)
utils/passkeys/create-passkey.ts (3)

46-49: ⚠️ Potential issue

Remove non-null assertion operator.

The non-null assertion operator (!) on subOrganizationId could lead to runtime errors if the value is undefined.

Apply this diff:

-      subOrganizationId!
+      subOrganizationId ?? throw new Error("SubOrganizationId is required")

52-55: 🛠️ Refactor suggestion

Enhance error handling with specific error cases.

The current error handling only logs and rethrows errors without providing meaningful user feedback.

Consider handling specific error cases:

} catch (e) {
  console.error("error during passkey creation", e);
  if (e instanceof WebAuthnError) {
    setStatusString("Failed to create passkey: User cancelled or device error");
  } else if (e instanceof SubOrgError) {
    setStatusString("Failed to create organization");
  } else {
    setStatusString("An unexpected error occurred");
  }
  throw e;
}

58-61: ⚠️ Potential issue

Prevent execution when passkeys are unsupported.

The function continues execution even when passkeys are not supported.

Apply this diff:

  if (!isSupported()) {
    alert("Passkeys are not supported on this device");
+   return;
  }
🧹 Nitpick comments (6)
utils/passkeys/create-passkey.ts (1)

65-65: Improve userId generation to prevent potential conflicts.

Using timestamp alone for userId could lead to conflicts if multiple requests occur simultaneously.

Consider using a more robust unique identifier:

- const userId = Buffer.from(String(Date.now())).toString("base64");
+ const userId = Buffer.from(
+   `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
+ ).toString("base64");
.github/workflows/build-internal.yml (1)

69-69: Consider grouping related environment variables together

While the addition of EXPO_PUBLIC_TURNKEY_ORG is correct, consider grouping it with other authentication-related variables like EXPO_PUBLIC_PRIVY_APP_ID for better organization.

Also, consider adding a validation check for the secret:

-          echo "EXPO_PUBLIC_TURNKEY_ORG=${{ secrets.EXPO_PUBLIC_TURNKEY_ORG }}" >> $env_file
+          if [ -z "${{ secrets.EXPO_PUBLIC_TURNKEY_ORG }}" ]; then
+            echo "Error: EXPO_PUBLIC_TURNKEY_ORG secret is not set"
+            exit 1
+          fi
+          echo "EXPO_PUBLIC_TURNKEY_ORG=${{ secrets.EXPO_PUBLIC_TURNKEY_ORG }}" >> $env_file
.github/workflows/build-app-release.yml (1)

51-51: Consider DRY principle for environment setup

The same environment variable setup is duplicated in both Android and iOS jobs. Consider extracting this into a reusable composite action or workflow template to maintain DRY principles.

Example approach:

# .github/actions/setup-env/action.yml
name: 'Setup Environment'
description: 'Sets up environment variables from secrets'
runs:
  using: "composite"
  steps:
    - run: |
        # Your environment setup script
        echo "EXPO_PUBLIC_CONTACT_ADDRESS=${{ secrets.EXPO_PUBLIC_CONTACT_ADDRESS }}" >> .env.production
        # ... other variables ...
        echo "EXPO_PUBLIC_TURNKEY_ORG=${{ secrets.EXPO_PUBLIC_TURNKEY_ORG }}" >> .env.production
      shell: bash

Then use it in both jobs:

- uses: ./.github/actions/setup-env

Also applies to: 95-95

screens/Onboarding/OnboardingPasskeyScreen.tsx (3)

36-68: Consider using operation-specific loading states.

Using a shared loading state for all operations could lead to race conditions if multiple operations are triggered simultaneously.

Consider implementing separate loading states for each operation:

-const loading = usePasskeyAuthStoreContext((state) => state.loading);
+const createLoading = usePasskeyAuthStoreContext((state) => state.createLoading);
+const loginLoading = usePasskeyAuthStoreContext((state) => state.loginLoading);
+const addWalletLoading = usePasskeyAuthStoreContext((state) => state.addWalletLoading);
+const xmtpLoading = usePasskeyAuthStoreContext((state) => state.xmtpLoading);

70-76: Enhance error handling with more descriptive messages.

While the error handling is consistent, consider providing more user-friendly error messages and specific error codes for better debugging.

Consider enhancing the error handling:

 const handleError = useCallback(
   (error: string) => {
+    const errorCode = generateErrorCode();
+    const userFriendlyMessage = translateError(error);
     setError(error);
-    captureErrorWithToast(error);
+    captureErrorWithToast({
+      message: userFriendlyMessage,
+      errorCode,
+      technicalDetails: error,
+    });
   },
   [setError]
 );

262-290: Update button loading states.

The buttons share a common loading state, which could lead to a confusing user experience.

 <Button
   text={"Onboard with passkey"}
   onPress={onboardWithPasskey}
-  loading={loading}
+  loading={createLoading || xmtpLoading}
 />
 <Button
   text={translate("passkey.createButton")}
   onPress={handleCreatePasskey}
-  loading={loading}
+  loading={createLoading}
   variant="secondary"
 />
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de66f20 and 4f84f97.

📒 Files selected for processing (6)
  • .github/workflows/build-app-release.yml (2 hunks)
  • .github/workflows/build-internal.yml (1 hunks)
  • screens/Onboarding/OnboardingPasskeyScreen.tsx (1 hunks)
  • scripts/build/ios/preview.js (1 hunks)
  • scripts/build/ios/production.js (1 hunks)
  • utils/passkeys/create-passkey.ts (1 hunks)
🔇 Additional comments (9)
utils/passkeys/create-passkey.ts (2)

1-16: LGTM! Well-structured imports and type definitions.

The imports are properly organized, and the PasskeyCreateParams type is well-defined with clear parameter types.


67-80: LGTM! Well-documented configuration.

The code includes clear and helpful comments explaining the purpose of each configuration option.

.github/workflows/build-app-release.yml (2)

Line range hint 8-14: Verify the removal of scheduled builds

The removal of scheduled builds (Tuesdays and Thursdays) appears intentional based on the comment. Please confirm this aligns with the team's current release strategy.


53-53: ⚠️ Potential issue

Fix typo in build script path

There's a typo in the script path that will cause the build to fail:

-        run: node scripts/Øbuild/eas.js --env production
+        run: node scripts/build/eas.js --env production

Likely invalid or redundant comment.

scripts/build/ios/production.js (1)

93-97: LGTM! Verify domain configuration for passkey support.

The addition of web credentials for the production domain is correct and aligns with Apple's requirements for passkey implementation.

Please ensure that:

  1. The domain converse.xyz has the appropriate Apple App Site Association (AASA) file configured
  2. The AASA file includes the webcredentials component with the correct app ID
  3. The file is accessible at https://converse.xyz/.well-known/apple-app-site-association

Run this script to verify the AASA configuration:

✅ Verification successful

✅ Domain configuration verified for passkey support

The Apple App Site Association file is properly configured with the required webcredentials component and correct app IDs.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify AASA file configuration for passkey support
# Note: The actual file content should be validated manually as it may contain sensitive information

# Check if AASA file is accessible and properly formatted
curl -v https://converse.xyz/.well-known/apple-app-site-association -H "Accept: application/json"

Length of output: 2937

scripts/build/ios/preview.js (1)

120-124: LGTM! Verify preview domain configuration and update documentation.

The web credentials configuration for the preview environment is correct and maintains proper environment separation.

Please ensure that:

  1. The domain preview.converse.xyz has the appropriate AASA file configured
  2. The AASA file includes the webcredentials component with the correct app ID
  3. The file is accessible at https://preview.converse.xyz/.well-known/apple-app-site-association

Consider adding documentation in the script's header comment section about the passkey-related domain configurations and their requirements for each environment.

Run this script to verify the AASA configuration:

✅ Verification successful

AASA configuration verified successfully for preview.converse.xyz

The Apple App Site Association file is properly configured with the correct webcredentials for both app bundles in the preview environment.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify AASA file configuration for preview environment
# Note: The actual file content should be validated manually as it may contain sensitive information

# Check if AASA file is accessible and properly formatted
curl -v https://preview.converse.xyz/.well-known/apple-app-site-association -H "Accept: application/json"

Length of output: 3084

screens/Onboarding/OnboardingPasskeyScreen.tsx (3)

1-31: LGTM! Well-structured component setup.

The component follows React best practices with proper separation of concerns, memoization, and clear organization of imports.


160-190: 🛠️ Refactor suggestion

Improve error handling in createXmtpClientFromAccount.

The error handling uses string concatenation and console.log, which could be improved.

     } catch (err) {
-      console.log("error creating Xmtp client", err);
+      const errorMessage = err instanceof Error ? err.message : 
+        typeof err === "string" ? err : "Unknown error";
       setStatusString("");
-      handleError(
-        "Error creating Xmtp client : " +
-          (err instanceof Error ? err.message : "") +
-          (typeof err === "string" ? err : "")
-      );
+      handleError(`Error creating Xmtp client: ${errorMessage}`);
     }

Likely invalid or redundant comment.


78-103: ⚠️ Potential issue

Add input validation for passkey name.

The passkey creation lacks validation for the input name, which could lead to security issues.

Add validation before creating the passkey:

 const handleCreatePasskey = useCallback(async () => {
   try {
     setLoading(true);
+    const passkeyName = inputTextRef.current ?? "";
+    if (!passkeyName.trim()) {
+      throw new Error("Passkey name is required");
+    }
+    if (passkeyName.length < 3) {
+      throw new Error("Passkey name must be at least 3 characters");
+    }
+    if (!/^[a-zA-Z0-9-_]+$/.test(passkeyName)) {
+      throw new Error("Passkey name can only contain letters, numbers, hyphens and underscores");
+    }
     const account = await onPasskeyCreate({
-      passkeyName: inputTextRef.current ?? "",
+      passkeyName,
       setStatusString,
       setPreviousPasskeyName,
       setTurnkeyInfo,
     });

Likely invalid or redundant comment.

Comment on lines +244 to +249
{turnkeyInfo && (
<Text preset="bodyBold">
Turnkey info:
<Text preset="body">{JSON.stringify(turnkeyInfo)}</Text>
</Text>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove sensitive data from UI.

Displaying raw turnkey info in the UI could expose sensitive data.

Consider removing or masking sensitive information:

-      {turnkeyInfo && (
-        <Text preset="bodyBold">
-          Turnkey info:
-          <Text preset="body">{JSON.stringify(turnkeyInfo)}</Text>
-        </Text>
-      )}
+      {turnkeyInfo && (
+        <Text preset="bodyBold">
+          Turnkey status:
+          <Text preset="body">Connected</Text>
+        </Text>
+      )}
📝 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
{turnkeyInfo && (
<Text preset="bodyBold">
Turnkey info:
<Text preset="body">{JSON.stringify(turnkeyInfo)}</Text>
</Text>
)}
{turnkeyInfo && (
<Text preset="bodyBold">
Turnkey status:
<Text preset="body">Connected</Text>
</Text>
)}

Comment on lines +256 to +261
<TextField
placeholder="Enter your passkey"
onChangeText={(text) => {
inputTextRef.current = text;
}}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add accessibility support to TextField.

The text field lacks accessibility props, which could make it difficult for users with disabilities.

 <TextField
   placeholder="Enter your passkey"
+  accessibilityLabel="Passkey input field"
+  accessibilityHint="Enter a name for your passkey"
   onChangeText={(text) => {
     inputTextRef.current = text;
   }}
+  returnKeyType="done"
+  autoCapitalize="none"
+  autoCorrect={false}
 />
📝 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
<TextField
placeholder="Enter your passkey"
onChangeText={(text) => {
inputTextRef.current = text;
}}
/>
<TextField
placeholder="Enter your passkey"
accessibilityLabel="Passkey input field"
accessibilityHint="Enter a name for your passkey"
onChangeText={(text) => {
inputTextRef.current = text;
}}
returnKeyType="done"
autoCapitalize="none"
autoCorrect={false}
/>

Comment on lines 112 to 130
const handleLoginWithPasskey = useCallback(async () => {
try {
setLoading(true);
const { account } = await loadAccountFromPasskey({
setStatusString,
setPreviousPasskeyName,
setTurnkeyInfo,
});
if (!account) {
handleError("No account loaded from Passkey");
throw new Error("No account loaded from Passkey");
}
setAccount(account);
} catch (e) {
handleError(e instanceof Error ? e.message : "Unknown error");
} finally {
setLoading(false);
}
}, [
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove redundant error handling in handleLoginWithPasskey.

The function throws an error after already handling it with handleError, which is redundant.

       if (!account) {
         handleError("No account loaded from Passkey");
-        throw new Error("No account loaded from Passkey");
         return;
       }
📝 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
const handleLoginWithPasskey = useCallback(async () => {
try {
setLoading(true);
const { account } = await loadAccountFromPasskey({
setStatusString,
setPreviousPasskeyName,
setTurnkeyInfo,
});
if (!account) {
handleError("No account loaded from Passkey");
throw new Error("No account loaded from Passkey");
}
setAccount(account);
} catch (e) {
handleError(e instanceof Error ? e.message : "Unknown error");
} finally {
setLoading(false);
}
}, [
const handleLoginWithPasskey = useCallback(async () => {
try {
setLoading(true);
const { account } = await loadAccountFromPasskey({
setStatusString,
setPreviousPasskeyName,
setTurnkeyInfo,
});
if (!account) {
handleError("No account loaded from Passkey");
return;
}
setAccount(account);
} catch (e) {
handleError(e instanceof Error ? e.message : "Unknown error");
} finally {
setLoading(false);
}
}, [

Comment on lines +192 to +224
const onboardWithPasskey = useCallback(async () => {
try {
setLoading(true);
const account = await onPasskeyCreate({
passkeyName: inputTextRef.current ?? "",
setStatusString,
setPreviousPasskeyName,
setTurnkeyInfo,
});
await initXmtpClientFromViemAccount({
account,
address: account.address,
});
if (isMissingConverseProfile()) {
router.navigate("OnboardingUserProfile");
} else if (needToShowNotificationsPermissions()) {
router.navigate("OnboardingNotifications");
} else {
setAuthStatus("signedIn");
}
} catch (e) {
handleError(e instanceof Error ? e.message : "Unknown error");
} finally {
setLoading(false);
}
}, [
setLoading,
setStatusString,
setPreviousPasskeyName,
setTurnkeyInfo,
router,
handleError,
]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract common onboarding logic to avoid duplication.

The onboardWithPasskey function duplicates logic from handleCreatePasskey and createXmtpClientFromAccount.

Consider extracting the common logic:

+const handleNavigation = useCallback(() => {
+  if (isMissingConverseProfile()) {
+    router.navigate("OnboardingUserProfile");
+  } else if (needToShowNotificationsPermissions()) {
+    router.navigate("OnboardingNotifications");
+  } else {
+    setAuthStatus("signedIn");
+  }
+}, [router]);

 const onboardWithPasskey = useCallback(async () => {
   try {
     setLoading(true);
-    const account = await onPasskeyCreate({
-      passkeyName: inputTextRef.current ?? "",
-      setStatusString,
-      setPreviousPasskeyName,
-      setTurnkeyInfo,
-    });
+    const account = await handleCreatePasskey();
+    if (!account) return;
     await initXmtpClientFromViemAccount({
       account,
       address: account.address,
     });
-    if (isMissingConverseProfile()) {
-      router.navigate("OnboardingUserProfile");
-    } else if (needToShowNotificationsPermissions()) {
-      router.navigate("OnboardingNotifications");
-    } else {
-      setAuthStatus("signedIn");
-    }
+    handleNavigation();
   } catch (e) {

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (5)
screens/Onboarding/OnboardingPasskeyScreen.tsx (5)

114-132: 🛠️ Refactor suggestion

Remove redundant error handling.

The function throws an error after already handling it with handleError, which is redundant.

   if (!account) {
     handleError("No account loaded from Passkey");
-    throw new Error("No account loaded from Passkey");
     return;
   }

162-192: 🛠️ Refactor suggestion

Improve error logging and string concatenation.

Replace console.log with logger utility and improve error message construction.

-      console.log("error creating Xmtp client", err);
+      logger.error("Error creating Xmtp client:", err);
       setStatusString("");
       handleError(
-        "Error creating Xmtp client : " +
-          (err instanceof Error ? err.message : "") +
-          (typeof err === "string" ? err : "")
+        `Error creating Xmtp client: ${
+          err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error"
+        }`
       );

194-226: 🛠️ Refactor suggestion

Extract common navigation logic.

The navigation logic is duplicated between createXmtpClientFromAccount and onboardWithPasskey.

Extract the common logic to a shared function as suggested in the previous review.


246-251: ⚠️ Potential issue

Remove sensitive data from UI.

Displaying raw turnkey info in the UI could expose sensitive data.

Consider masking sensitive information as suggested in the previous review.


258-263: 🛠️ Refactor suggestion

Add accessibility support to TextField.

The text field lacks accessibility props, which could make it difficult for users with disabilities.

Add accessibility props as suggested in the previous review.

🧹 Nitpick comments (3)
screens/Onboarding/OnboardingPasskeyScreen.tsx (2)

71-79: Consider adding error codes for better tracking.

While the error handling is comprehensive, adding error codes would improve error tracking and debugging.

-const handleError = useCallback(
+const handleError = useCallback(
+  (error: string, code?: string) => {
+    const errorWithCode = code ? `[${code}] ${error}` : error;
     logger.error(error);
-    setError(error);
+    setError(errorWithCode);
-    captureErrorWithToast(error);
+    captureErrorWithToast(errorWithCode);
   },
   [setError]
 );

264-292: Update button loading states.

The buttons should use operation-specific loading states to prevent UI inconsistencies.

 <Button
   text={"Onboard with passkey"}
   onPress={onboardWithPasskey}
-  loading={loading}
+  loading={createLoading}
 />
 <Button
   text={translate("passkey.createButton")}
   onPress={handleCreatePasskey}
-  loading={loading}
+  loading={createLoading}
   variant="secondary"
 />
screens/ConversationList.tsx (1)

278-278: Consider memoizing socials data

The addition of socials to the useLayoutEffect dependencies could trigger unnecessary header re-renders. Consider:

  1. Memoizing the socials data if it doesn't change frequently
  2. Using a ref if the avatar URI doesn't need to be reactive
- const { data: socials } = useProfileSocials(currentAccount!);
+ const { data: socials } = React.useMemo(
+   () => useProfileSocials(currentAccount!),
+   [currentAccount]
+ );

Also applies to: 451-451

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3007dcb and d79e3b5.

📒 Files selected for processing (4)
  • app.json (1 hunks)
  • screens/ConversationList.tsx (4 hunks)
  • screens/NewAccount/NewAccountPasskeyScreen.tsx (1 hunks)
  • screens/Onboarding/OnboardingPasskeyScreen.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app.json
  • screens/NewAccount/NewAccountPasskeyScreen.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build-with-eas
🔇 Additional comments (4)
screens/Onboarding/OnboardingPasskeyScreen.tsx (2)

1-32: LGTM! Well-structured component setup.

The component is properly structured with appropriate memoization and context provider setup.


34-70: Consider using operation-specific loading states.

The shared loading state could lead to race conditions when multiple operations are triggered in quick succession.

Consider using operation-specific loading states:

+const [createLoading, setCreateLoading] = useState(false);
+const [loginLoading, setLoginLoading] = useState(false);
+const [addWalletLoading, setAddWalletLoading] = useState(false);
+const [xmtpLoading, setXmtpLoading] = useState(false);
screens/ConversationList.tsx (2)

62-63: LGTM! Clean import additions

The new imports follow the project's conventions and are properly organized.


328-331: Verify avatar fallback behavior

While the Avatar implementation looks good, ensure that it gracefully handles cases where:

  • socials data is undefined/null
  • getPreferredAvatar returns null/undefined
  • the avatar URI is invalid or inaccessible

Let's verify the Avatar component's implementation:

✅ Verification successful

Avatar implementation properly handles all edge cases

The implementation includes comprehensive error handling for:

  • undefined/null socials data
  • missing or invalid avatar URIs
  • fallback to ENS avatars
  • display of initials when no avatar is available
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check Avatar component implementation for fallback handling
ast-grep --pattern 'const Avatar = $_($$$) => {
  $$$
}'

# Check getPreferredAvatar implementation for null handling
ast-grep --pattern 'const getPreferredAvatar = $_($$$) => {
  $$$
}'

Length of output: 129


Script:

#!/bin/bash
# First, find relevant files
echo "=== Files containing Avatar ==="
rg -l "Avatar" --type tsx --type ts

echo -e "\n=== Avatar component implementation ==="
rg "Avatar.*=.*{" --type tsx --type ts -A 10

echo -e "\n=== getPreferredAvatar implementation ==="
rg "getPreferredAvatar.*=.*{" --type tsx --type ts -A 10

Length of output: 636


Script:

#!/bin/bash
# Search for Avatar component
echo "=== Avatar Component ==="
rg "export.*Avatar" -A 10

echo -e "\n=== Avatar Usage and Props ==="
rg "Avatar.*size.*uri" -A 5

echo -e "\n=== getPreferredAvatar Function ==="
rg "getPreferredAvatar" -A 5

Length of output: 24796

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
utils/api.ts (1)

830-847: ⚠️ Potential issue

Critical: Restore App Check headers and add error handling.

  1. The App Check headers are commented out, which removes a crucial security measure for the wallet creation endpoint.
  2. The function lacks error handling for potential failures during wallet creation.

Apply this diff to restore the headers and add error handling:

 export const createTurnkeySubOrganization = async (
   authenticatorParams: TurnkeyAuthenticatorParams
 ): Promise<{
   subOrganizationId: string;
   walletId: string;
   address: string;
 }> => {
+  try {
     const { data } = await api.post(
       "/api/wallet/create-wallet-turnkey",
       {
         authenticatorParams,
       },
       {
-        // headers: await getAppCheckTokenHeaders(),
+        headers: await getAppCheckTokenHeaders(),
       }
     );
     return data;
+  } catch (error) {
+    logger.error("[API] Failed to create Turnkey wallet", error);
+    throw new Error(
+      error instanceof Error
+        ? error.message
+        : "Failed to create wallet. Please try again."
+    );
+  }
 };
🧹 Nitpick comments (2)
screens/Onboarding/OnboardingPasskeyScreen.tsx (2)

26-32: Add TypeScript types for component props.

Consider adding explicit type definitions for better type safety and documentation:

-export const OnboardingPasskeyScreen = memo(function Screen() {
+type OnboardingPasskeyScreenProps = Record<string, never>;
+export const OnboardingPasskeyScreen = memo(function Screen(_props: OnboardingPasskeyScreenProps) {

71-78: Enhance error handling with error categories.

Consider categorizing errors to provide more specific user feedback:

+type ErrorCategory = 'passkey' | 'network' | 'auth' | 'unknown';
+
+const categorizeError = (error: string): ErrorCategory => {
+  if (error.includes('passkey')) return 'passkey';
+  if (error.includes('network')) return 'network';
+  if (error.includes('auth')) return 'auth';
+  return 'unknown';
+};
+
 const handleError = useCallback(
   (error: string) => {
     logger.error(error);
-    setError(error);
+    const category = categorizeError(error);
+    setError(`[${category}] ${error}`);
     captureErrorWithToast(error);
   },
   [setError]
 );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f10047 and 2d3b40f.

📒 Files selected for processing (3)
  • screens/Onboarding/OnboardingPasskeyScreen.tsx (1 hunks)
  • utils/api.ts (3 hunks)
  • utils/appCheck.ts (1 hunks)
🔇 Additional comments (7)
utils/api.ts (1)

273-281: LGTM! Well-structured header generation.

The function properly handles token retrieval and error cases.

screens/Onboarding/OnboardingPasskeyScreen.tsx (6)

122-125: Remove redundant error handling.

The error is both handled and thrown, which is redundant.


204-236: Extract common onboarding logic to reduce duplication.

The onboardWithPasskey function duplicates logic from handleCreatePasskey and createXmtpClientFromAccount.


191-199: Improve error message formatting in createXmtpClientFromAccount.

The error handling is inconsistent with other handlers and uses console.log.


256-261: Remove sensitive data from UI.

Displaying raw turnkey info in the UI could expose sensitive data.


268-273: Add accessibility support to TextField.

The text field lacks accessibility props, which could make it difficult for users with disabilities.


274-302: ⚠️ Potential issue

Prevent multiple simultaneous operations.

The loading state is shared across all operations which could lead to race conditions.

+const [operationInProgress, setOperationInProgress] = useState<string | null>(null);
+
+const withOperation = async (operation: string, callback: () => Promise<void>) => {
+  if (operationInProgress) {
+    return; // Prevent multiple operations
+  }
+  try {
+    setOperationInProgress(operation);
+    setLoading(true);
+    await callback();
+  } finally {
+    setLoading(false);
+    setOperationInProgress(null);
+  }
+};

 <Button
   text={"Onboard with passkey"}
   onPress={onboardWithPasskey}
-  loading={loading}
+  loading={loading && operationInProgress === 'onboard'}
   />

Likely invalid or redundant comment.

Comment on lines 17 to 19
// const { token } = await appCheck.getLimitedUseToken();
// const { token } = await appCheck.getToken();
return token;
return "";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: App Check token verification has been bypassed.

Returning an empty string instead of a valid App Check token circumvents a crucial security mechanism. App Check is designed to verify that requests originate from legitimate instances of your application, protecting your backend resources from abuse.

Restore the token retrieval logic:

-    // const { token } = await appCheck.getLimitedUseToken();
-    // const { token } = await appCheck.getToken();
-    return "";
+    const { token } = await appCheck.getToken();
+    return token;
📝 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
// const { token } = await appCheck.getLimitedUseToken();
// const { token } = await appCheck.getToken();
return token;
return "";
const { token } = await appCheck.getToken();
return token;

(state) => state.setPreviousPasskeyName
);

const inputTextRef = useRef<string>("");
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation for passkey name.

The current implementation allows empty strings. Consider adding validation and minimum length requirements for the passkey name.

-const inputTextRef = useRef<string>("");
+const inputTextRef = useRef<string>("");
+const validatePasskeyName = (name: string): boolean => {
+  return name.length >= 3; // Minimum 3 characters
+};

Committable suggestion skipped: line range outside the PR's diff.

Removed appcheck
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
utils/appCheck.ts (1)

17-19: ⚠️ Potential issue

Critical: App Check token verification has been bypassed

Returning a hardcoded string instead of a valid App Check token circumvents a crucial security mechanism. App Check is designed to verify that requests originate from legitimate instances of your application, protecting your backend resources from abuse.

Let's verify the backend dependencies on App Check:

#!/bin/bash
# Check backend middleware for App Check dependencies
rg -A 10 "app-?check" --type ts

# Check for protected endpoints
ast-grep --pattern 'app.use($$$appCheck$$$)'

Restore the token retrieval logic:

-    // const { token } = await appCheck.getLimitedUseToken();
-    // const { token } = await appCheck.getToken();
-    return "Placeholder";
+    const { token } = await appCheck.getToken();
+    return token;
screens/NewAccount/NewAccountPasskeyScreen.tsx (3)

174-181: ⚠️ Potential issue

Improve error handling in createXmtpClientFromAccount

The error handling concatenates different error types unsafely and uses console.log instead of the logger.

-      console.log("error creating Xmtp client", err);
+      logger.error("Error creating Xmtp client:", err);
       setStatusString("");
-      handleError(
-        "Error creating Xmtp client : " +
-          (err instanceof Error ? err.message : "") +
-          (typeof err === "string" ? err : "")
-      );
+      handleError(
+        `Error creating Xmtp client: ${
+          err instanceof Error ? err.message : 
+          typeof err === "string" ? err : 
+          "Unknown error"
+        }`
+      );

279-284: ⚠️ Potential issue

Security: Remove sensitive information from UI

Displaying turnkey information in the UI could pose a security risk if screenshots are taken or if the screen is visible to others.

-      {turnkeyInfo && (
-        <Text preset="bodyBold">
-          Turnkey info:
-          <Text preset="body">{JSON.stringify(turnkeyInfo)}</Text>
-        </Text>
-      )}
+      {turnkeyInfo && (
+        <Text preset="bodyBold">
+          Passkey configured successfully
+        </Text>
+      )}

291-296: ⚠️ Potential issue

Add input validation for passkey name

The TextField for passkey name lacks validation and error handling.

 <TextField
   placeholder="Enter your passkey"
+  error={inputTextRef.current?.length < 3 ? "Passkey name must be at least 3 characters" : undefined}
   onChangeText={(text) => {
     inputTextRef.current = text;
   }}
+  onBlur={() => {
+    if (!inputTextRef.current) {
+      setError("Passkey name is required");
+    }
+  }}
 />
🧹 Nitpick comments (1)
screens/NewAccount/NewAccountPasskeyScreen.tsx (1)

19-20: Consider consolidating error handling utilities

The error handling utilities (captureErrorWithToast and logger) could be combined into a single error handling service for better maintainability.

-import { captureErrorWithToast } from "@/utils/capture-error";
-import logger from "@/utils/logger";
+import { ErrorService } from "@/services/error";
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d3b40f and f381532.

📒 Files selected for processing (3)
  • screens/NewAccount/NewAccountPasskeyScreen.tsx (1 hunks)
  • screens/Onboarding/OnboardingPasskeyScreen.tsx (1 hunks)
  • utils/appCheck.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • screens/Onboarding/OnboardingPasskeyScreen.tsx
🔇 Additional comments (1)
screens/NewAccount/NewAccountPasskeyScreen.tsx (1)

297-331: 🛠️ Refactor suggestion

Implement separate loading states for different operations

Currently, all buttons share the same loading state, which could lead to unclear UI feedback when multiple operations are in progress.

+  const [createLoading, setCreateLoading] = useState(false);
+  const [loginLoading, setLoginLoading] = useState(false);
+  const [addWalletLoading, setAddWalletLoading] = useState(false);
+  const [xmtpLoading, setXmtpLoading] = useState(false);

   // Update button loading props
   <Button
     text={"Onboard with passkey"}
     onPress={onboardWithPasskey}
-    loading={loading}
+    loading={createLoading}
   />
   <Button
     text={"Add new wallet to existing passkey"}
     onPress={addWalletToExistingPasskey}
-    loading={loading}
+    loading={addWalletLoading}
   />

Likely invalid or redundant comment.

Comment on lines +72 to +79
const handleError = useCallback(
(error: string) => {
logger.error(error);
setError(error);
captureErrorWithToast(error);
},
[setError]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling with better type safety and messages

The current error handling implementation could be improved with better type safety and more descriptive error messages.

-  const handleError = useCallback(
-    (error: string) => {
+  const handleError = useCallback(
+    (error: Error | string) => {
+      const errorMessage = error instanceof Error 
+        ? `${error.name}: ${error.message}` 
+        : error;
-      logger.error(error);
-      setError(error);
-      captureErrorWithToast(error);
+      logger.error(errorMessage);
+      setError(errorMessage);
+      captureErrorWithToast(errorMessage);
     },
-    [setError]
+    [setError]
   );
📝 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
const handleError = useCallback(
(error: string) => {
logger.error(error);
setError(error);
captureErrorWithToast(error);
},
[setError]
);
const handleError = useCallback(
(error: Error | string) => {
const errorMessage = error instanceof Error
? `${error.name}: ${error.message}`
: error;
logger.error(errorMessage);
setError(errorMessage);
captureErrorWithToast(errorMessage);
},
[setError]
);

@alexrisch alexrisch deployed to Production January 9, 2025 07:51 — with GitHub Actions Active
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
theme/useAppTheme.ts (1)

154-154: Remove commented type definition.

Since navTheme has been removed from the implementation, the commented type definition should also be removed for clarity.

-  // navTheme: typeof DefaultTheme;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7753a8a and 5c9ba52.

📒 Files selected for processing (2)
  • screens/Main.tsx (1 hunks)
  • theme/useAppTheme.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • screens/Main.tsx
🔇 Additional comments (2)
theme/useAppTheme.ts (2)

212-215: LGTM! Comprehensive debug logging.

The debug logging changes are well-structured and include all necessary information for debugging theme-related issues. The dependency array correctly includes all referenced variables.


Line range hint 180-190: Verify impact of theme management changes.

The shift to system color scheme-based theme management is a significant architectural change. While it improves consistency, we should verify that existing theme-dependent components continue to work as expected.

✅ Verification successful

Theme management changes are architecturally sound

The shift to system color scheme-based theme management maintains backward compatibility while improving consistency:

  • Existing components continue to access theme properties through the same API
  • Fallback mechanism ensures consistent theme values
  • Theme override capability is preserved
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for components using theme-related hooks
echo "Components using useAppTheme:"
rg "useAppTheme" --type ts --type tsx -l

echo "\nComponents using useTheme:"
rg "useTheme" --type ts --type tsx -l

echo "\nComponents directly accessing theme context:"
rg "ThemeContext" --type ts --type tsx -l

Length of output: 443


Script:

#!/bin/bash
echo "=== Components using useAppTheme ==="
rg "useAppTheme" -A 2 -B 2

echo -e "\n=== Components using useTheme ==="
rg "useTheme" -A 2 -B 2

echo -e "\n=== Components using ThemeContext ==="
rg "ThemeContext" -A 2 -B 2

Length of output: 66351

Comment on lines +189 to +190
() => overrideTheme || (controlledTheme ? "dark" : "light"),
[overrideTheme, controlledTheme]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect theme context determination logic.

The current condition controlledTheme ? "dark" : "light" will set the theme to "dark" for both "dark" and "light" system color schemes, as both are truthy values. This could lead to unexpected behavior.

-    () => overrideTheme || (controlledTheme ? "dark" : "light"),
+    () => overrideTheme || controlledTheme || "light",
📝 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
() => overrideTheme || (controlledTheme ? "dark" : "light"),
[overrideTheme, controlledTheme]
() => overrideTheme || controlledTheme || "light",
[overrideTheme, controlledTheme]

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