diff --git a/.changeset/hot-emus-join.md b/.changeset/hot-emus-join.md
new file mode 100644
index 000000000..db2cccf15
--- /dev/null
+++ b/.changeset/hot-emus-join.md
@@ -0,0 +1,5 @@
+---
+"@crossmint/client-sdk-react-native-ui": minor
+---
+
+Added new built-in UI for email and phone signers, can use 'headlessSigningFlow' to disable.
diff --git a/.changeset/pink-owls-prove.md b/.changeset/pink-owls-prove.md
new file mode 100644
index 000000000..5f1269cd6
--- /dev/null
+++ b/.changeset/pink-owls-prove.md
@@ -0,0 +1,5 @@
+---
+"@crossmint/client-sdk-react-base": patch
+---
+
+Consolidated UI state within base provider.
diff --git a/.changeset/selfish-comics-grow.md b/.changeset/selfish-comics-grow.md
new file mode 100644
index 000000000..1d184857d
--- /dev/null
+++ b/.changeset/selfish-comics-grow.md
@@ -0,0 +1,5 @@
+---
+"@crossmint/client-sdk-react-ui": patch
+---
+
+Updated CrossmintWalletProvider to consume shared state management from base provider, improving consistency across React implementations.
\ No newline at end of file
diff --git a/apps/wallets/smart-wallet/expo/app/_layout.tsx b/apps/wallets/smart-wallet/expo/app/_layout.tsx
index 752649585..5a8666878 100644
--- a/apps/wallets/smart-wallet/expo/app/_layout.tsx
+++ b/apps/wallets/smart-wallet/expo/app/_layout.tsx
@@ -20,7 +20,12 @@ function CrossmintProviders({ children }: { children: ReactNode }) {
     return (
         
             
-                {children}
+                
+                    {children}
+                
             
         
     );
diff --git a/apps/wallets/smart-wallet/expo/app/index.tsx b/apps/wallets/smart-wallet/expo/app/index.tsx
index c4d80bd32..03987ccda 100644
--- a/apps/wallets/smart-wallet/expo/app/index.tsx
+++ b/apps/wallets/smart-wallet/expo/app/index.tsx
@@ -1,35 +1,25 @@
-import {
-    useCrossmintAuth,
-    useWallet,
-    useWalletEmailSigner,
-    useCrossmint,
-    type Balances,
-    ExportPrivateKeyButton,
-} from "@crossmint/client-sdk-react-native-ui";
+import { useWallet, useAuth, type Balances } from "@crossmint/client-sdk-react-native-ui";
 import { useEffect, useMemo, useState } from "react";
 import { ActivityIndicator, Button, Text, View, TextInput, StyleSheet, ScrollView, Alert } from "react-native";
 import * as Linking from "expo-linking";
 import { fundUSDC } from "@/utils/usdcFaucet";
+import { HeadlessSigning } from "@/components/headless-signing";
 
 export default function Index() {
-    const { loginWithOAuth, user, logout, createAuthSession, jwt } = useCrossmintAuth();
-    const { experimental_customAuth } = useCrossmint();
-    const loggedInUserEmail = experimental_customAuth?.email ?? null;
-    const { wallet, getOrCreateWallet, status: walletStatus } = useWallet();
-    const { needsAuth, sendEmailWithOtp, verifyOtp, reject } = useWalletEmailSigner();
+    const { loginWithOAuth, user, logout, createAuthSession, jwt } = useAuth();
+    const { wallet, status: walletStatus } = useWallet();
     const walletAddress = useMemo(() => wallet?.address, [wallet]);
     const url = Linking.useURL();
 
     const [balances, setBalances] = useState(null);
     const [isLoading, setIsLoading] = useState(false);
 
-    // Email signer states
-    const [otp, setOtp] = useState("");
     const [txLink, setTxLink] = useState(null);
-    const [uiError, setUiError] = useState(null);
     const [recipientAddress, setRecipientAddress] = useState("");
     const [amount, setAmount] = useState("");
 
+    console.log("wallet", wallet);
+
     useEffect(() => {
         if (url != null) {
             createAuthSession(url);
@@ -51,37 +41,6 @@ export default function Index() {
         fetchBalances();
     }, [wallet]);
 
-    const handleAction = async (action: () => Promise | void) => {
-        setIsLoading(true);
-        setUiError(null);
-        setTxLink(null);
-        try {
-            await action();
-        } catch (e: any) {
-            console.error(e);
-            const message = e.message || "An unexpected error occurred.";
-            setUiError(message);
-            Alert.alert("Error", message);
-        } finally {
-            setIsLoading(false);
-        }
-    };
-
-    async function initWallet() {
-        if (user == null) {
-            console.log("User not logged in");
-            return;
-        }
-        setIsLoading(true);
-        try {
-            await getOrCreateWallet({ chain: "base-sepolia", signer: { type: "email" } });
-        } catch (error) {
-            console.error("Error initializing wallet:", error);
-        } finally {
-            setIsLoading(false);
-        }
-    }
-
     async function onHandleFundUSDC() {
         if (walletAddress == null) {
             console.log("Wallet address not found");
@@ -101,26 +60,6 @@ export default function Index() {
         }
     }
 
-    const handleSendOtpEmail = async () => {
-        if (typeof loggedInUserEmail !== "string") {
-            Alert.alert("Error", "User email is not available.");
-            return;
-        }
-
-        await handleAction(sendEmailWithOtp);
-    };
-
-    const handleVerifyOtpInput = async () => {
-        if (!otp || !verifyOtp) {
-            Alert.alert("Error", "Please enter the OTP and ensure email signer is available.");
-            return;
-        }
-        await handleAction(async () => {
-            await verifyOtp(otp);
-            setOtp("");
-        });
-    };
-
     async function sendUSDC() {
         if (walletStatus !== "loaded" || wallet == null) {
             Alert.alert("Error", "Wallet is not loaded or is not a Solana smart wallet.");
@@ -148,59 +87,47 @@ export default function Index() {
                 User: {user?.email}
                 Wallet: {walletAddress}
                 Auth Status: {walletStatus}
-                Needs OTP Auth: {needsAuth ? "Yes" : "No"}
                 
                     Native Token Balance: ({balances?.nativeToken.symbol}) {balances?.nativeToken.amount}
                 
                 USDC Balance: {balances?.usdc.amount}
-                {uiError && Last Action Error: {uiError}}
                 {txLink && Last Tx Link: {txLink}}
             
 
             
-                
-                
+                {walletAddress != null && (
+                    
+                )}
+                {/*  */}
             
 
             
-                
-            
-
-            
-                
 
-            {needsAuth && (
-                
-                    Email OTP Verification (required)
-                    
-                    
-                    
-                     reject(new Error("Rejected OTP"))} />
-                
-            )}
+            {/* To test headless signing, 
+            1. import and uncomment 
+            2. add 'headlessSigningFlow' to CrossmintWalletProvider in _layout.tsx
+            3. remove 'createOnLogin' from CrossmintWalletProvider in _layout.tsx */}
+            
 
             {walletAddress != null && (
                 
@@ -255,8 +182,4 @@ const styles = StyleSheet.create({
         marginBottom: 10,
         borderRadius: 5,
     },
-    errorText: {
-        color: "red",
-        marginTop: 5,
-    },
 });
diff --git a/apps/wallets/smart-wallet/expo/components/headless-signing.tsx b/apps/wallets/smart-wallet/expo/components/headless-signing.tsx
new file mode 100644
index 000000000..58e7421e8
--- /dev/null
+++ b/apps/wallets/smart-wallet/expo/components/headless-signing.tsx
@@ -0,0 +1,113 @@
+import { useAuth, useCrossmint, useWallet, useWalletEmailSigner } from "@crossmint/client-sdk-react-native-ui";
+import { useState } from "react";
+import { Button, Text, View, TextInput, Alert, StyleSheet } from "react-native";
+
+export function HeadlessSigning() {
+    const { user } = useAuth();
+    const { experimental_customAuth } = useCrossmint();
+    const { getOrCreateWallet } = useWallet();
+    const loggedInUserEmail = experimental_customAuth?.email ?? null;
+    const { needsAuth, sendEmailWithOtp, verifyOtp, reject } = useWalletEmailSigner();
+
+    const [isLoading, setIsLoading] = useState(false);
+
+    // Email signer states
+    const [otp, setOtp] = useState("");
+    const [uiError, setUiError] = useState(null);
+
+    const handleAction = async (action: () => Promise | void) => {
+        setIsLoading(true);
+        setUiError(null);
+        try {
+            await action();
+        } catch (e: any) {
+            console.error(e);
+            const message = e.message || "An unexpected error occurred.";
+            setUiError(message);
+            Alert.alert("Error", message);
+        } finally {
+            setIsLoading(false);
+        }
+    };
+
+    async function initWallet() {
+        if (user == null) {
+            console.log("User not logged in");
+            return;
+        }
+        setIsLoading(true);
+        try {
+            await getOrCreateWallet({ chain: "base-sepolia", signer: { type: "email" } });
+        } catch (error) {
+            console.error("Error initializing wallet:", error);
+        } finally {
+            setIsLoading(false);
+        }
+    }
+
+    const handleSendOtpEmail = async () => {
+        if (typeof loggedInUserEmail !== "string") {
+            Alert.alert("Error", "User email is not available.");
+            return;
+        }
+
+        await handleAction(sendEmailWithOtp);
+    };
+
+    const handleVerifyOtpInput = async () => {
+        if (!otp || !verifyOtp) {
+            Alert.alert("Error", "Please enter the OTP and ensure email signer is available.");
+            return;
+        }
+        await handleAction(async () => {
+            await verifyOtp(otp);
+            setOtp("");
+        });
+    };
+    return (
+        
+            Headless Signing Component
+            Needs OTP Auth: {needsAuth ? "Yes" : "No"}
+            {uiError && Last Action Error: {uiError}}
+            {user && }
+
+            {needsAuth && (
+                
+                    Email OTP Verification (required)
+                    
+                    
+                    
+                     reject(new Error("Rejected OTP"))} />
+                
+            )}
+        
+    );
+}
+
+const styles = StyleSheet.create({
+    section: {
+        marginBottom: 20,
+        paddingVertical: 10,
+        borderBottomWidth: 1,
+        borderBottomColor: "#eee",
+        gap: 10,
+    },
+    input: {
+        borderWidth: 1,
+        borderColor: "#ccc",
+        padding: 10,
+        marginBottom: 10,
+        borderRadius: 5,
+    },
+    errorText: {
+        color: "red",
+        marginTop: 5,
+    },
+});
diff --git a/packages/client/react-base/src/hooks/index.ts b/packages/client/react-base/src/hooks/index.ts
index 0055d0801..f1d9887df 100644
--- a/packages/client/react-base/src/hooks/index.ts
+++ b/packages/client/react-base/src/hooks/index.ts
@@ -1,3 +1,4 @@
 export * from "./useCrossmint";
 export * from "./useWallet";
 export * from "./useAuth";
+export * from "./useSignerAuth";
diff --git a/packages/client/react-base/src/hooks/useSignerAuth.ts b/packages/client/react-base/src/hooks/useSignerAuth.ts
new file mode 100644
index 000000000..8c391f040
--- /dev/null
+++ b/packages/client/react-base/src/hooks/useSignerAuth.ts
@@ -0,0 +1,164 @@
+import { useState, useRef, useCallback, type MutableRefObject } from "react";
+import type { CreateOnLogin } from "@/types";
+
+const throwNotAvailable = (functionName: string) => () => {
+    throw new Error(`${functionName} is not available. Make sure you're using an email or phone signer wallet.`);
+};
+
+export type SignerType = "email" | "phone";
+export type DialogStep = "initial" | "otp";
+
+export interface SignerAuthState {
+    emailSignerDialogOpen: boolean;
+    emailSignerDialogStep: DialogStep;
+    setEmailSignerDialogOpen: (open: boolean) => void;
+    setEmailSignerDialogStep: (step: DialogStep) => void;
+
+    phoneSignerDialogOpen: boolean;
+    phoneSignerDialogStep: DialogStep;
+    setPhoneSignerDialogOpen: (open: boolean) => void;
+    setPhoneSignerDialogStep: (step: DialogStep) => void;
+
+    sendEmailWithOtpRef: MutableRefObject<() => Promise>;
+    verifyOtpRef: MutableRefObject<(otp: string) => Promise>;
+    sendPhoneWithOtpRef: MutableRefObject<() => Promise>;
+    verifyPhoneOtpRef: MutableRefObject<(otp: string) => Promise>;
+    rejectRef: MutableRefObject<(error?: Error) => void>;
+}
+
+export interface SignerAuthHandlers {
+    emailsigners_handleSendEmailOTP: () => Promise;
+    emailsigners_handleOTPSubmit: (otp: string) => Promise;
+    emailsigners_handleResendOTP: () => Promise;
+
+    phonesigners_handleSendPhoneOTP: () => Promise;
+    phonesigners_handleOTPSubmit: (otp: string) => Promise;
+    phonesigners_handleResendOTP: () => Promise;
+
+    onAuthRequired: (
+        needsAuth: boolean,
+        sendMessageWithOtp: () => Promise,
+        verifyOtp: (otp: string) => Promise,
+        reject: () => void
+    ) => Promise;
+}
+
+export function useSignerAuth(createOnLogin?: CreateOnLogin): SignerAuthState & SignerAuthHandlers {
+    const [emailSignerDialogOpen, setEmailSignerDialogOpen] = useState(false);
+    const [emailSignerDialogStep, setEmailSignerDialogStep] = useState("initial");
+    const [phoneSignerDialogOpen, setPhoneSignerDialogOpen] = useState(false);
+    const [phoneSignerDialogStep, setPhoneSignerDialogStep] = useState("initial");
+
+    const sendEmailWithOtpRef = useRef<() => Promise>(throwNotAvailable("sendEmailWithOtp"));
+    const verifyOtpRef = useRef<(otp: string) => Promise>(throwNotAvailable("verifyOtp"));
+    const sendPhoneWithOtpRef = useRef<() => Promise>(throwNotAvailable("sendPhoneWithOtp"));
+    const verifyPhoneOtpRef = useRef<(otp: string) => Promise>(throwNotAvailable("verifyPhoneOtp"));
+    const rejectRef = useRef<(error?: Error) => void>(throwNotAvailable("reject"));
+
+    const emailsigners_handleSendEmailOTP = useCallback(async () => {
+        try {
+            await sendEmailWithOtpRef.current();
+            setEmailSignerDialogStep("otp");
+        } catch (error) {
+            console.error("Failed to send email OTP", error);
+            rejectRef.current(new Error("Failed to send email OTP"));
+        }
+    }, []);
+
+    const emailsigners_handleOTPSubmit = useCallback(async (otp: string) => {
+        try {
+            await verifyOtpRef.current(otp);
+            setEmailSignerDialogOpen(false);
+            setEmailSignerDialogStep("initial");
+        } catch (error) {
+            console.error("Failed to verify OTP", error);
+            rejectRef.current(new Error("Failed to verify OTP"));
+        }
+    }, []);
+
+    const emailsigners_handleResendOTP = useCallback(async () => {
+        try {
+            await sendEmailWithOtpRef.current();
+        } catch (error) {
+            console.error("Failed to resend email OTP", error);
+            rejectRef.current(new Error("Failed to resend email OTP"));
+        }
+    }, []);
+
+    // Phone authentication handlers
+    const phonesigners_handleSendPhoneOTP = useCallback(async () => {
+        try {
+            await sendPhoneWithOtpRef.current();
+            setPhoneSignerDialogStep("otp");
+        } catch (error) {
+            console.error("Failed to send phone OTP", error);
+            rejectRef.current(new Error("Failed to send phone OTP"));
+        }
+    }, []);
+
+    const phonesigners_handleOTPSubmit = useCallback(async (otp: string) => {
+        try {
+            await verifyPhoneOtpRef.current(otp);
+            setPhoneSignerDialogOpen(false);
+            setPhoneSignerDialogStep("initial");
+        } catch (error) {
+            console.error("Failed to verify phone OTP", error);
+            rejectRef.current(new Error("Failed to verify phone OTP"));
+        }
+    }, []);
+
+    const phonesigners_handleResendOTP = useCallback(async () => {
+        try {
+            await sendPhoneWithOtpRef.current();
+        } catch (error) {
+            console.error("Failed to resend phone OTP", error);
+            rejectRef.current(new Error("Failed to resend phone OTP"));
+        }
+    }, []);
+
+    const onAuthRequired = useCallback(
+        async (
+            needsAuth: boolean,
+            sendMessageWithOtp: () => Promise,
+            verifyOtp: (otp: string) => Promise,
+            reject: () => void
+        ): Promise => {
+            if (createOnLogin?.signer.type === "phone" && createOnLogin.signer.phone) {
+                setPhoneSignerDialogOpen(needsAuth);
+                sendPhoneWithOtpRef.current = sendMessageWithOtp;
+                verifyPhoneOtpRef.current = verifyOtp;
+            } else {
+                setEmailSignerDialogOpen(needsAuth);
+                sendEmailWithOtpRef.current = sendMessageWithOtp;
+                verifyOtpRef.current = verifyOtp;
+            }
+            rejectRef.current = reject;
+        },
+        [createOnLogin?.signer.type, createOnLogin?.signer]
+    );
+
+    return {
+        emailSignerDialogOpen,
+        emailSignerDialogStep,
+        setEmailSignerDialogOpen,
+        setEmailSignerDialogStep,
+        phoneSignerDialogOpen,
+        phoneSignerDialogStep,
+        setPhoneSignerDialogOpen,
+        setPhoneSignerDialogStep,
+
+        sendEmailWithOtpRef,
+        verifyOtpRef,
+        sendPhoneWithOtpRef,
+        verifyPhoneOtpRef,
+        rejectRef,
+
+        emailsigners_handleSendEmailOTP,
+        emailsigners_handleOTPSubmit,
+        emailsigners_handleResendOTP,
+        phonesigners_handleSendPhoneOTP,
+        phonesigners_handleOTPSubmit,
+        phonesigners_handleResendOTP,
+        onAuthRequired,
+    };
+}
diff --git a/packages/client/react-base/src/providers/CrossmintWalletBaseProvider.tsx b/packages/client/react-base/src/providers/CrossmintWalletBaseProvider.tsx
index 400914da1..635b338f1 100644
--- a/packages/client/react-base/src/providers/CrossmintWalletBaseProvider.tsx
+++ b/packages/client/react-base/src/providers/CrossmintWalletBaseProvider.tsx
@@ -20,6 +20,12 @@ export type CrossmintWalletBaseContext = {
     getOrCreateWallet: (props: WalletArgsFor) => Promise | undefined>;
     onAuthRequired?: EmailSignerConfig["onAuthRequired"] | PhoneSignerConfig["onAuthRequired"];
     clientTEEConnection?: () => HandshakeParent;
+    emailSignerState: {
+        needsAuth: boolean;
+        sendEmailWithOtp: (() => Promise) | null;
+        verifyOtp: ((otp: string) => Promise) | null;
+        reject: ((error?: Error) => void) | null;
+    };
 };
 
 export const CrossmintWalletBaseContext = createContext({
@@ -28,6 +34,12 @@ export const CrossmintWalletBaseContext = createContext Promise.resolve(undefined),
     onAuthRequired: undefined,
     clientTEEConnection: undefined,
+    emailSignerState: {
+        needsAuth: false,
+        sendEmailWithOtp: null,
+        verifyOtp: null,
+        reject: null,
+    },
 });
 
 export interface CrossmintWalletBaseProviderProps {
@@ -56,6 +68,34 @@ export function CrossmintWalletBaseProvider({
     const [wallet, setWallet] = useState | undefined>(undefined);
     const [walletStatus, setWalletStatus] = useState<"not-loaded" | "in-progress" | "loaded" | "error">("not-loaded");
 
+    const [emailSignerState, setEmailSignerState] = useState({
+        needsAuth: false,
+        sendEmailWithOtp: null as (() => Promise) | null,
+        verifyOtp: null as ((otp: string) => Promise) | null,
+        reject: null as ((error?: Error) => void) | null,
+    });
+
+    const wrappedOnAuthRequired = useCallback(
+        async (
+            needsAuth: boolean,
+            sendEmailWithOtp: () => Promise,
+            verifyOtp: (otp: string) => Promise,
+            reject: () => void
+        ) => {
+            setEmailSignerState({
+                needsAuth,
+                sendEmailWithOtp,
+                verifyOtp,
+                reject,
+            });
+
+            if (onAuthRequired) {
+                return await onAuthRequired(needsAuth, sendEmailWithOtp, verifyOtp, reject);
+            }
+        },
+        [onAuthRequired]
+    );
+
     const getOrCreateWallet = useCallback(
         async (_args: WalletArgsFor) => {
             // Deep clone the args object to avoid mutating the original object
@@ -76,7 +116,7 @@ export function CrossmintWalletBaseProvider({
 
                 if (args?.signer?.type === "email") {
                     const email = args.signer.email ?? experimental_customAuth?.email;
-                    const _onAuthRequired = args.signer.onAuthRequired ?? onAuthRequired;
+                    const _onAuthRequired = args.signer.onAuthRequired ?? wrappedOnAuthRequired;
 
                     if (email == null) {
                         throw new Error(
@@ -92,7 +132,7 @@ export function CrossmintWalletBaseProvider({
 
                 if (args?.signer?.type === "phone") {
                     const phone = args.signer.phone ?? experimental_customAuth?.phone;
-                    const _onAuthRequired = args.signer.onAuthRequired ?? onAuthRequired;
+                    const _onAuthRequired = args.signer.onAuthRequired ?? wrappedOnAuthRequired;
 
                     if (phone == null) {
                         throw new Error("Phone not found in signer. Please set phone in signer.");
@@ -144,7 +184,17 @@ export function CrossmintWalletBaseProvider({
                 return undefined;
             }
         },
-        [crossmint, experimental_customAuth]
+        [
+            crossmint,
+            experimental_customAuth,
+            wrappedOnAuthRequired,
+            walletStatus,
+            wallet,
+            callbacks?.onWalletCreationStart,
+            callbacks?.onTransactionStart,
+            clientTEEConnection,
+            initializeWebView,
+        ]
     );
 
     useEffect(() => {
@@ -179,10 +229,11 @@ export function CrossmintWalletBaseProvider({
             wallet,
             status: walletStatus,
             getOrCreateWallet,
-            onAuthRequired,
+            onAuthRequired: wrappedOnAuthRequired,
             clientTEEConnection,
+            emailSignerState,
         }),
-        [getOrCreateWallet, wallet, walletStatus, onAuthRequired, clientTEEConnection]
+        [getOrCreateWallet, wallet, walletStatus, wrappedOnAuthRequired, clientTEEConnection, emailSignerState]
     );
 
     return {children};
diff --git a/packages/client/react-base/src/providers/CrossmintWalletUIBaseProvider.tsx b/packages/client/react-base/src/providers/CrossmintWalletUIBaseProvider.tsx
new file mode 100644
index 000000000..aacdb2760
--- /dev/null
+++ b/packages/client/react-base/src/providers/CrossmintWalletUIBaseProvider.tsx
@@ -0,0 +1,177 @@
+import { type ReactNode, useCallback, useState, type MutableRefObject } from "react";
+import type { UIConfig } from "@crossmint/common-sdk-base";
+import type { HandshakeParent } from "@crossmint/client-sdk-window";
+import type { signerInboundEvents, signerOutboundEvents } from "@crossmint/client-signers";
+import { CrossmintWalletBaseProvider } from "./CrossmintWalletBaseProvider";
+import { useCrossmint } from "@/hooks/useCrossmint";
+import { useSignerAuth } from "@/hooks/useSignerAuth";
+import type { CreateOnLogin } from "@/types";
+
+export interface CrossmintWalletUIBaseProviderProps {
+    children: ReactNode;
+    appearance?: UIConfig;
+    createOnLogin?: CreateOnLogin;
+    headlessSigningFlow?: boolean;
+    showPasskeyHelpers?: boolean;
+    callbacks?: {
+        onWalletCreationStart?: () => Promise;
+        onTransactionStart?: () => Promise;
+    };
+    initializeWebView?: () => Promise;
+    renderUI?: (props: UIRenderProps) => ReactNode;
+    clientTEEConnection?: () => HandshakeParent;
+}
+
+export interface UIRenderProps {
+    emailSignerProps: {
+        email?: string;
+        open: boolean;
+        setOpen: (open: boolean) => void;
+        step: "initial" | "otp";
+        onSubmitOTP: (token: string) => Promise;
+        onResendOTPCode: () => Promise;
+        onSubmitEmail: () => Promise;
+        rejectRef: MutableRefObject<(error?: Error) => void>;
+        appearance?: UIConfig;
+    };
+
+    phoneSignerProps: {
+        phone?: string;
+        open: boolean;
+        setOpen: (open: boolean) => void;
+        step: "initial" | "otp";
+        onSubmitOTP: (token: string) => Promise;
+        onResendOTPCode: () => Promise;
+        onSubmitPhone: () => Promise;
+        rejectRef: MutableRefObject<(error?: Error) => void>;
+        appearance?: UIConfig;
+    };
+
+    passkeyPromptProps?: {
+        state: {
+            open: boolean;
+            type?: ValidPasskeyPromptType;
+            primaryActionOnClick?: () => void;
+            secondaryActionOnClick?: () => void;
+        };
+        appearance?: UIConfig;
+    };
+}
+
+type ValidPasskeyPromptType =
+    | "create-wallet"
+    | "transaction"
+    | "not-supported"
+    | "create-wallet-error"
+    | "transaction-error";
+
+type PasskeyPromptState = {
+    open: boolean;
+    type?: ValidPasskeyPromptType;
+    primaryActionOnClick?: () => void;
+    secondaryActionOnClick?: () => void;
+};
+
+export function CrossmintWalletUIBaseProvider({
+    children,
+    appearance,
+    createOnLogin,
+    headlessSigningFlow,
+    showPasskeyHelpers = true,
+    callbacks,
+    initializeWebView,
+    renderUI,
+    clientTEEConnection,
+}: CrossmintWalletUIBaseProviderProps) {
+    const { experimental_customAuth } = useCrossmint();
+    const [passkeyPromptState, setPasskeyPromptState] = useState({ open: false });
+
+    const signerAuth = useSignerAuth(createOnLogin);
+
+    const email =
+        createOnLogin?.signer.type === "email" && createOnLogin?.signer.email != null
+            ? createOnLogin.signer.email
+            : experimental_customAuth?.email;
+
+    const phoneNumber =
+        createOnLogin?.signer.type === "phone" && createOnLogin?.signer.phone != null
+            ? createOnLogin.signer.phone
+            : experimental_customAuth?.phone;
+
+    const createPasskeyPrompt = useCallback(
+        (type: ValidPasskeyPromptType) => () =>
+            new Promise((resolve) => {
+                if (!showPasskeyHelpers) {
+                    resolve();
+                    return;
+                }
+                setPasskeyPromptState({
+                    type,
+                    open: true,
+                    primaryActionOnClick: () => {
+                        setPasskeyPromptState({ open: false });
+                        resolve();
+                    },
+                    secondaryActionOnClick: () => {
+                        setPasskeyPromptState({ open: false });
+                        resolve();
+                    },
+                });
+            }),
+        [showPasskeyHelpers]
+    );
+
+    const getCallbacks = useCallback(() => {
+        let onWalletCreationStart = callbacks?.onWalletCreationStart;
+        let onTransactionStart = callbacks?.onTransactionStart;
+
+        if (createOnLogin?.signer.type === "passkey" && showPasskeyHelpers) {
+            onWalletCreationStart = createPasskeyPrompt("create-wallet");
+            onTransactionStart = createPasskeyPrompt("transaction");
+        }
+
+        return { onWalletCreationStart, onTransactionStart };
+    }, [callbacks, createOnLogin?.signer.type, showPasskeyHelpers, createPasskeyPrompt]);
+
+    const uiRenderProps: UIRenderProps = {
+        emailSignerProps: {
+            email,
+            open: signerAuth.emailSignerDialogOpen && email != null,
+            setOpen: signerAuth.setEmailSignerDialogOpen,
+            step: signerAuth.emailSignerDialogStep,
+            onSubmitOTP: signerAuth.emailsigners_handleOTPSubmit,
+            onResendOTPCode: signerAuth.emailsigners_handleResendOTP,
+            onSubmitEmail: signerAuth.emailsigners_handleSendEmailOTP,
+            rejectRef: signerAuth.rejectRef,
+            appearance,
+        },
+        phoneSignerProps: {
+            phone: phoneNumber,
+            open: signerAuth.phoneSignerDialogOpen && phoneNumber != null,
+            setOpen: signerAuth.setPhoneSignerDialogOpen,
+            step: signerAuth.phoneSignerDialogStep,
+            onSubmitOTP: signerAuth.phonesigners_handleOTPSubmit,
+            onResendOTPCode: signerAuth.phonesigners_handleResendOTP,
+            onSubmitPhone: signerAuth.phonesigners_handleSendPhoneOTP,
+            rejectRef: signerAuth.rejectRef,
+            appearance,
+        },
+        passkeyPromptProps: {
+            state: passkeyPromptState,
+            appearance,
+        },
+    };
+
+    return (
+        
+            {children}
+            {!headlessSigningFlow && renderUI != null && renderUI(uiRenderProps)}
+        
+    );
+}
diff --git a/packages/client/react-base/src/providers/index.ts b/packages/client/react-base/src/providers/index.ts
index eefd2475b..ddef8a6a9 100644
--- a/packages/client/react-base/src/providers/index.ts
+++ b/packages/client/react-base/src/providers/index.ts
@@ -1,3 +1,4 @@
 export * from "./CrossmintWalletBaseProvider";
+export * from "./CrossmintWalletUIBaseProvider";
 export * from "./CrossmintProvider";
 export { CrossmintAuthBaseProvider, CrossmintAuthBaseContext, useCrossmintAuthBase } from "./CrossmintAuthBaseProvider";
diff --git a/packages/client/ui/react-native/package.json b/packages/client/ui/react-native/package.json
index 874be2632..e9f2e5b38 100644
--- a/packages/client/ui/react-native/package.json
+++ b/packages/client/ui/react-native/package.json
@@ -24,7 +24,9 @@
         "@crossmint/wallets-sdk": "workspace:*",
         "@solana/web3.js": "1.98.1",
         "bs58": "^5.0.0",
+        "lucide-react-native": "0.548.0",
         "mitt": "3.0.1",
+        "react-native-svg": "15.14.0",
         "zod": "3.22.4"
     },
     "devDependencies": {
@@ -37,11 +39,11 @@
     },
     "peerDependencies": {
         "@expo/config-plugins": ">=9.0.0",
+        "@solana/web3.js": "^1.98.1",
         "expo-constants": ">=17 <19",
         "expo-device": ">=7 <9",
         "expo-secure-store": ">=14 <16",
         "expo-web-browser": ">=14 <16",
-        "@solana/web3.js": "^1.98.1",
         "react": ">=17.0.2",
         "react-native": ">=0.74.3",
         "react-native-webview": ">=13.15.0 <14"
diff --git a/packages/client/ui/react-native/src/components/index.ts b/packages/client/ui/react-native/src/components/index.ts
index fc7407e0f..46dfa38b2 100644
--- a/packages/client/ui/react-native/src/components/index.ts
+++ b/packages/client/ui/react-native/src/components/index.ts
@@ -1,2 +1,3 @@
 export * from "./embed";
+export * from "./signers";
 export * from "./wallets";
diff --git a/packages/client/ui/react-native/src/components/signers/BaseCodeInput.tsx b/packages/client/ui/react-native/src/components/signers/BaseCodeInput.tsx
new file mode 100644
index 000000000..5078914e5
--- /dev/null
+++ b/packages/client/ui/react-native/src/components/signers/BaseCodeInput.tsx
@@ -0,0 +1,220 @@
+import { useState, type ReactNode } from "react";
+import { View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator } from "react-native";
+import type { UIConfig } from "@crossmint/common-sdk-base";
+import { theme } from "../../styles/theme";
+
+interface BaseCodeInputProps {
+    contactInfo: string;
+    contactType: "email" | "phone";
+    icon: ReactNode;
+    title: string;
+    description: string | ReactNode;
+    helpText: string;
+    onSubmitOTP: (token: string) => Promise;
+    onResendCode?: () => Promise;
+    appearance?: UIConfig;
+    otpLength?: number;
+    keyboardType?: "default" | "number-pad";
+    autoComplete?: "one-time-code" | "sms-otp";
+    textContentType?: "oneTimeCode";
+}
+
+export function BaseCodeInput({
+    icon,
+    title,
+    description,
+    helpText,
+    onSubmitOTP,
+    onResendCode,
+    appearance,
+    otpLength = 9,
+    keyboardType = "default",
+    autoComplete = "one-time-code",
+    textContentType = "oneTimeCode",
+}: BaseCodeInputProps) {
+    const [otpCode, setOtpCode] = useState("");
+    const [isLoading, setIsLoading] = useState(false);
+    const [error, setError] = useState(null);
+    const [resendCooldown, setResendCooldown] = useState(0);
+
+    const handleSubmitOTP = async () => {
+        if (otpCode.length === 0) {
+            return;
+        }
+
+        setIsLoading(true);
+        setError(null);
+        try {
+            await onSubmitOTP(otpCode);
+            setOtpCode("");
+        } catch (error) {
+            console.error("Failed to verify OTP", error);
+            setError("Invalid code. Please try again.");
+        } finally {
+            setIsLoading(false);
+        }
+    };
+
+    const handleResendCode = async () => {
+        if (resendCooldown > 0 || !onResendCode) {
+            return;
+        }
+
+        setIsLoading(true);
+        setError(null);
+        try {
+            await onResendCode();
+            setResendCooldown(60);
+            const timer = setInterval(() => {
+                setResendCooldown((prev) => {
+                    if (prev <= 1) {
+                        clearInterval(timer);
+                        return 0;
+                    }
+                    return prev - 1;
+                });
+            }, 1000);
+        } catch (error) {
+            console.error("Failed to resend OTP", error);
+            setError("Failed to resend code. Please try again.");
+        } finally {
+            setIsLoading(false);
+        }
+    };
+
+    const dynamicStyles = StyleSheet.create({
+        container: {
+            width: "100%",
+        },
+        iconContainer: {
+            alignItems: "center",
+            marginBottom: 8,
+        },
+        title: {
+            fontSize: 18,
+            fontWeight: "600",
+            color: appearance?.colors?.textPrimary || theme["cm-text-primary"],
+            textAlign: "center",
+            marginBottom: 8,
+            marginTop: 16,
+        },
+        description: {
+            fontSize: 14,
+            color: appearance?.colors?.textSecondary || theme["cm-text-secondary"],
+            textAlign: "center",
+            marginBottom: 24,
+            lineHeight: 20,
+        },
+        otpInput: {
+            borderWidth: 1,
+            borderColor: error
+                ? appearance?.colors?.danger || theme["cm-danger"]
+                : appearance?.colors?.border || theme["cm-border"],
+            borderRadius: appearance?.borderRadius || 12,
+            backgroundColor: appearance?.colors?.inputBackground || theme["cm-muted-primary"],
+            padding: 16,
+            fontSize: 16,
+            color: appearance?.colors?.textPrimary || theme["cm-text-primary"],
+            marginBottom: 16,
+            textAlign: "center",
+        },
+        errorText: {
+            fontSize: 14,
+            color: appearance?.colors?.danger || theme["cm-danger"],
+            textAlign: "center",
+            marginBottom: 16,
+        },
+        helpText: {
+            fontSize: 12,
+            color: appearance?.colors?.textSecondary || theme["cm-text-secondary"],
+            textAlign: "center",
+            marginBottom: 16,
+            lineHeight: 16,
+        },
+        submitButton: {
+            backgroundColor: appearance?.colors?.accent || theme["cm-accent"],
+            paddingVertical: 16,
+            paddingHorizontal: 32,
+            borderRadius: appearance?.borderRadius || 12,
+            alignItems: "center",
+            marginBottom: 16,
+        },
+        submitButtonDisabled: {
+            opacity: 0.6,
+        },
+        submitButtonText: {
+            fontSize: 16,
+            fontWeight: "500",
+            color: appearance?.colors?.background || theme["cm-background-primary"],
+        },
+        resendButton: {
+            alignSelf: "center",
+            paddingVertical: 12,
+            paddingHorizontal: 16,
+        },
+        resendButtonText: {
+            fontSize: 14,
+            color: appearance?.colors?.accent || theme["cm-accent"],
+            textAlign: "center",
+        },
+        resendButtonDisabled: {
+            opacity: 0.6,
+        },
+    });
+
+    return (
+        
+            {icon}
+
+            {title}
+            {description}
+
+             {
+                    setOtpCode(text);
+                    setError(null);
+                }}
+                keyboardType={keyboardType}
+                autoComplete={autoComplete}
+                textContentType={textContentType}
+                editable={!isLoading}
+                maxLength={otpLength}
+            />
+
+            {error && {error}}
+
+            {helpText}
+
+            
+                {isLoading ? (
+                    
+                ) : (
+                    Submit
+                )}
+            
+
+            {onResendCode && (
+                 0 && dynamicStyles.resendButtonDisabled]}
+                    onPress={handleResendCode}
+                    disabled={resendCooldown > 0 || isLoading}
+                >
+                    
+                        {resendCooldown > 0 ? `Re-send code in ${resendCooldown}s` : "Re-send code"}
+                    
+                
+            )}
+        
+    );
+}
diff --git a/packages/client/ui/react-native/src/components/signers/BaseConfirmation.tsx b/packages/client/ui/react-native/src/components/signers/BaseConfirmation.tsx
new file mode 100644
index 000000000..e701c1bf4
--- /dev/null
+++ b/packages/client/ui/react-native/src/components/signers/BaseConfirmation.tsx
@@ -0,0 +1,154 @@
+import { useState, type ReactNode } from "react";
+import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator } from "react-native";
+import type { UIConfig } from "@crossmint/common-sdk-base";
+import { theme } from "../../styles/theme";
+
+interface BaseConfirmationProps {
+    contactInfo: string;
+    contactType: "email" | "phone";
+    icon: ReactNode;
+    onConfirm: () => Promise;
+    onCancel?: () => void;
+    appearance?: UIConfig;
+}
+
+export function BaseConfirmation({
+    contactInfo,
+    contactType,
+    icon,
+    onConfirm,
+    onCancel,
+    appearance,
+}: BaseConfirmationProps) {
+    const [isLoading, setIsLoading] = useState(false);
+    const [error, setError] = useState(null);
+
+    const handleConfirm = async () => {
+        setIsLoading(true);
+        setError(null);
+        try {
+            await onConfirm();
+        } catch (error) {
+            console.error(`Failed to send ${contactType} code`, error);
+            setError(`Failed to send ${contactType === "email" ? "email" : "SMS"}. Please try again.`);
+        } finally {
+            setIsLoading(false);
+        }
+    };
+
+    const dynamicStyles = StyleSheet.create({
+        container: {
+            width: "100%",
+        },
+        title: {
+            fontSize: 24,
+            fontWeight: "600",
+            color: appearance?.colors?.textPrimary || theme["cm-text-primary"],
+            textAlign: "center",
+            marginBottom: 8,
+        },
+        description: {
+            fontSize: 16,
+            color: appearance?.colors?.textSecondary || theme["cm-text-secondary"],
+            textAlign: "center",
+            marginBottom: 24,
+            lineHeight: 22,
+        },
+        contactContainer: {
+            borderWidth: 1,
+            borderColor: appearance?.colors?.border || theme["cm-border"],
+            borderRadius: appearance?.borderRadius || 12,
+            padding: 16,
+            marginBottom: 24,
+            flexDirection: "row",
+            alignItems: "center",
+        },
+        contactText: {
+            fontSize: 16,
+            color: appearance?.colors?.textPrimary || theme["cm-text-primary"],
+            marginLeft: 12,
+            flex: 1,
+        },
+        errorText: {
+            fontSize: 14,
+            color: appearance?.colors?.danger || theme["cm-danger"],
+            textAlign: "center",
+            marginBottom: 16,
+        },
+        buttonContainer: {
+            flexDirection: "row",
+            gap: 12,
+        },
+        button: {
+            flex: 1,
+            paddingVertical: 16,
+            paddingHorizontal: 24,
+            borderRadius: 999,
+            alignItems: "center",
+            justifyContent: "center",
+            minHeight: 52,
+        },
+        primaryButton: {
+            backgroundColor: appearance?.colors?.accent || theme["cm-accent"],
+        },
+        secondaryButton: {
+            backgroundColor: "transparent",
+            borderWidth: 1,
+            borderColor: appearance?.colors?.border || theme["cm-border"],
+        },
+        disabledButton: {
+            opacity: 0.6,
+        },
+        buttonText: {
+            fontSize: 16,
+            fontWeight: "500",
+            color: appearance?.colors?.background || theme["cm-background-primary"],
+        },
+        secondaryButtonText: {
+            color: appearance?.colors?.textPrimary || theme["cm-text-primary"],
+        },
+    });
+
+    return (
+        
+            Confirm it's you
+            
+                You're using this wallet for the first time on this device. Click 'Send code' to get a one-time
+                verification code{contactType === "phone" ? " via SMS" : ""}.
+            
+
+            
+                {icon}
+                {contactInfo}
+            
+
+            {error && {error}}
+
+            
+                
+                    Cancel
+                
+
+                
+                    {isLoading ? (
+                        
+                    ) : (
+                        Send code
+                    )}
+                
+            
+        
+    );
+}
diff --git a/packages/client/ui/react-native/src/components/signers/EmailSignersDialog.tsx b/packages/client/ui/react-native/src/components/signers/EmailSignersDialog.tsx
new file mode 100644
index 000000000..f38507608
--- /dev/null
+++ b/packages/client/ui/react-native/src/components/signers/EmailSignersDialog.tsx
@@ -0,0 +1,155 @@
+import type { MutableRefObject } from "react";
+import { View, Modal, StyleSheet, Dimensions, TouchableOpacity, Text } from "react-native";
+import type { UIConfig } from "@crossmint/common-sdk-base";
+import { MailCheckIcon, MailIcon, X } from "lucide-react-native";
+import { BaseConfirmation } from "./BaseConfirmation";
+import { BaseCodeInput } from "./BaseCodeInput";
+import { theme } from "../../styles/theme";
+
+interface EmailSignersDialogProps {
+    email?: string;
+    open: boolean;
+    setOpen: (open: boolean) => void;
+    step: "initial" | "otp";
+    onSubmitOTP: (token: string) => Promise;
+    onResendOTPCode: () => Promise;
+    onSubmitEmail: () => Promise;
+    rejectRef: MutableRefObject<((error: Error) => void) | undefined>;
+    appearance?: UIConfig;
+}
+
+const { width: screenWidth } = Dimensions.get("window");
+
+export function EmailSignersDialog({
+    email,
+    open,
+    setOpen,
+    step,
+    onSubmitOTP,
+    onResendOTPCode,
+    onSubmitEmail,
+    rejectRef,
+    appearance,
+}: EmailSignersDialogProps) {
+    function handleOnCancel() {
+        if (open) {
+            rejectRef.current?.(new Error());
+            setOpen(false);
+        }
+    }
+
+    const dynamicStyles = StyleSheet.create({
+        centeredView: {
+            flex: 1,
+            backgroundColor: "rgba(0, 0, 0, 0.5)",
+            justifyContent: "center",
+            alignItems: "center",
+            padding: 16,
+        },
+        modalView: {
+            backgroundColor: appearance?.colors?.background || theme["cm-background-primary"],
+            borderRadius: appearance?.borderRadius || 12,
+            padding: 32,
+            alignItems: "center",
+            width: Math.min(screenWidth - 32, 400),
+            maxHeight: "80%",
+            position: "relative",
+            shadowColor: "#000",
+            shadowOffset: {
+                width: 0,
+                height: 2,
+            },
+            shadowOpacity: 0.25,
+            shadowRadius: 4,
+            elevation: 5,
+        },
+        closeButton: {
+            position: "absolute",
+            top: 16,
+            right: 16,
+            width: 32,
+            height: 32,
+            borderRadius: 6,
+            backgroundColor: appearance?.colors?.inputBackground || theme["cm-muted-primary"],
+            alignItems: "center",
+            justifyContent: "center",
+            zIndex: 1,
+        },
+    });
+
+    if (!open) {
+        return null;
+    }
+
+    return (
+        
+            
+                
+                    
+                        
+                    
+
+                    {step === "initial" ? (
+                        
+                            }
+                            onConfirm={onSubmitEmail}
+                            onCancel={handleOnCancel}
+                            appearance={appearance}
+                        />
+                    ) : (
+                        
+                                    
+                                
+                            }
+                            title="Check your email"
+                            description={
+                                
+                                    A temporary login code has been sent to{" "}
+                                    {email}
+                                
+                            }
+                            helpText={`Can't find the email? Check spam folder.\nSome emails may take several minutes to arrive.`}
+                            onSubmitOTP={onSubmitOTP}
+                            onResendCode={onResendOTPCode}
+                            appearance={appearance}
+                            otpLength={9}
+                            keyboardType="default"
+                            autoComplete="one-time-code"
+                            textContentType="oneTimeCode"
+                        />
+                    )}
+                
+            
+        
+    );
+}
diff --git a/packages/client/ui/react-native/src/components/signers/PhoneSignersDialog.tsx b/packages/client/ui/react-native/src/components/signers/PhoneSignersDialog.tsx
new file mode 100644
index 000000000..700be6a9e
--- /dev/null
+++ b/packages/client/ui/react-native/src/components/signers/PhoneSignersDialog.tsx
@@ -0,0 +1,155 @@
+import type { MutableRefObject } from "react";
+import { View, StyleSheet, Dimensions, Modal, TouchableOpacity, Text } from "react-native";
+import type { UIConfig } from "@crossmint/common-sdk-base";
+import { PhoneIcon, Smartphone, X } from "lucide-react-native";
+import { BaseConfirmation } from "./BaseConfirmation";
+import { BaseCodeInput } from "./BaseCodeInput";
+import { theme } from "../../styles/theme";
+
+interface PhoneSignersDialogProps {
+    phone?: string;
+    open: boolean;
+    setOpen: (open: boolean) => void;
+    step: "initial" | "otp";
+    onSubmitOTP: (token: string) => Promise;
+    onResendOTPCode: () => Promise;
+    onSubmitPhone: () => Promise;
+    rejectRef: MutableRefObject<((error: Error) => void) | undefined>;
+    appearance?: UIConfig;
+}
+
+const { width: screenWidth } = Dimensions.get("window");
+
+export function PhoneSignersDialog({
+    phone,
+    open,
+    setOpen,
+    step,
+    onSubmitOTP,
+    onResendOTPCode,
+    onSubmitPhone,
+    rejectRef,
+    appearance,
+}: PhoneSignersDialogProps) {
+    function handleOnCancel() {
+        if (open) {
+            rejectRef.current?.(new Error("User cancelled"));
+            setOpen(false);
+        }
+    }
+
+    const dynamicStyles = StyleSheet.create({
+        modalOverlay: {
+            flex: 1,
+            backgroundColor: "rgba(0, 0, 0, 0.5)",
+            justifyContent: "center",
+            alignItems: "center",
+            padding: 16,
+        },
+        modalContainer: {
+            backgroundColor: appearance?.colors?.background || theme["cm-background-primary"],
+            borderRadius: appearance?.borderRadius || 12,
+            padding: 32,
+            width: Math.min(screenWidth - 32, 400),
+            maxHeight: "80%",
+            position: "relative",
+            alignItems: "center",
+            shadowColor: "#000",
+            shadowOffset: {
+                width: 0,
+                height: 2,
+            },
+            shadowOpacity: 0.25,
+            shadowRadius: 4,
+            elevation: 5,
+        },
+        closeButton: {
+            position: "absolute",
+            top: 16,
+            right: 16,
+            width: 32,
+            height: 32,
+            borderRadius: 6,
+            backgroundColor: appearance?.colors?.inputBackground || theme["cm-muted-primary"],
+            alignItems: "center",
+            justifyContent: "center",
+            zIndex: 1,
+        },
+    });
+
+    if (!open) {
+        return null;
+    }
+
+    return (
+        
+            
+                
+                    
+                        
+                    
+
+                    {step === "initial" ? (
+                        
+                            }
+                            onConfirm={onSubmitPhone}
+                            onCancel={handleOnCancel}
+                            appearance={appearance}
+                        />
+                    ) : (
+                        
+                                    
+                                
+                            }
+                            title="Check your phone"
+                            description={
+                                
+                                    A temporary login code has been sent via SMS to{" "}
+                                    {phone}
+                                
+                            }
+                            helpText={`Can't receive the SMS? Check your phone number.\nSome messages may take several minutes to arrive.`}
+                            onSubmitOTP={onSubmitOTP}
+                            onResendCode={onResendOTPCode}
+                            appearance={appearance}
+                            otpLength={10}
+                            keyboardType="number-pad"
+                            autoComplete="sms-otp"
+                            textContentType="oneTimeCode"
+                        />
+                    )}
+                
+            
+        
+    );
+}
diff --git a/packages/client/ui/react-native/src/components/signers/index.ts b/packages/client/ui/react-native/src/components/signers/index.ts
new file mode 100644
index 000000000..e4dd05fed
--- /dev/null
+++ b/packages/client/ui/react-native/src/components/signers/index.ts
@@ -0,0 +1,2 @@
+export { EmailSignersDialog } from "./EmailSignersDialog";
+export { PhoneSignersDialog } from "./PhoneSignersDialog";
diff --git a/packages/client/ui/react-native/src/hooks/useWalletEmailSigner.ts b/packages/client/ui/react-native/src/hooks/useWalletEmailSigner.ts
index 9a4ec572f..dd5644f93 100644
--- a/packages/client/ui/react-native/src/hooks/useWalletEmailSigner.ts
+++ b/packages/client/ui/react-native/src/hooks/useWalletEmailSigner.ts
@@ -1,5 +1,9 @@
-import { CrossmintWalletEmailSignerContext } from "@/providers/CrossmintWalletProvider";
-import { useContext } from "react";
+import { useCallback, useContext } from "react";
+import { CrossmintWalletBaseContext } from "@crossmint/client-sdk-react-base";
+
+const throwNotAvailable = (functionName: string) => () => {
+    throw new Error(`${functionName} is not available. Make sure you're using an email signer wallet.`);
+};
 
 export type EmailSignerFunctions = {
     needsAuth: boolean;
@@ -9,20 +13,45 @@ export type EmailSignerFunctions = {
 };
 
 export function useWalletEmailSigner(): EmailSignerFunctions {
-    const context = useContext(CrossmintWalletEmailSignerContext);
+    const context = useContext(CrossmintWalletBaseContext);
 
     if (context == null) {
         throw new Error("useWalletEmailSigner must be used within CrossmintWalletProvider");
     }
 
-    if (!context.sendEmailWithOtp || !context.verifyOtp || !context.reject) {
-        throw new Error("Email signer functions are not available. Make sure you're using an email signer wallet.");
-    }
+    const { emailSignerState } = context;
+
+    const sendEmailWithOtp = useCallback(async () => {
+        if (!emailSignerState.sendEmailWithOtp) {
+            throwNotAvailable("sendEmailWithOtp")();
+        }
+        return await emailSignerState.sendEmailWithOtp?.();
+    }, [emailSignerState.sendEmailWithOtp]);
+
+    const verifyOtp = useCallback(
+        async (otp: string) => {
+            if (!emailSignerState.verifyOtp) {
+                throwNotAvailable("verifyOtp")();
+            }
+            return await emailSignerState.verifyOtp?.(otp);
+        },
+        [emailSignerState.verifyOtp]
+    );
+
+    const reject = useCallback(
+        (error: Error) => {
+            if (!emailSignerState.reject) {
+                throwNotAvailable("reject")();
+            }
+            emailSignerState.reject?.(error);
+        },
+        [emailSignerState.reject]
+    );
 
     return {
-        needsAuth: context.needsAuth,
-        sendEmailWithOtp: context.sendEmailWithOtp,
-        verifyOtp: context.verifyOtp,
-        reject: context.reject,
+        needsAuth: emailSignerState.needsAuth,
+        sendEmailWithOtp,
+        verifyOtp,
+        reject,
     };
 }
diff --git a/packages/client/ui/react-native/src/providers/CrossmintWalletProvider.tsx b/packages/client/ui/react-native/src/providers/CrossmintWalletProvider.tsx
index 2bb65fb87..bcdeb97de 100644
--- a/packages/client/ui/react-native/src/providers/CrossmintWalletProvider.tsx
+++ b/packages/client/ui/react-native/src/providers/CrossmintWalletProvider.tsx
@@ -1,41 +1,37 @@
-import { type ReactNode, useCallback, useEffect, useRef, useMemo, createContext, useState } from "react";
+import { type ReactNode, useCallback, useRef, useMemo, useEffect, useState, type RefObject } from "react";
 import { View } from "react-native";
 import type { WebView, WebViewMessageEvent } from "react-native-webview";
 import { RNWebView, WebViewParent } from "@crossmint/client-sdk-rn-window";
 import { environmentUrlConfig, signerInboundEvents, signerOutboundEvents } from "@crossmint/client-signers";
-import { validateAPIKey } from "@crossmint/common-sdk-base";
-import { type CreateOnLogin, CrossmintWalletBaseProvider } from "@crossmint/client-sdk-react-base";
-import { useCrossmint } from "@/hooks";
-
-const throwNotAvailable = (functionName: string) => () => {
-    throw new Error(`${functionName} is not available. Make sure you're using an email signer wallet.`);
-};
-
-type CrossmintWalletEmailSignerContext = {
-    needsAuth: boolean;
-    sendEmailWithOtp: () => Promise;
-    verifyOtp: (otp: string) => Promise;
-    reject: (error?: Error) => void;
-};
-
-// Create the auth context
-export const CrossmintWalletEmailSignerContext = createContext({
-    needsAuth: false,
-    sendEmailWithOtp: throwNotAvailable("sendEmailWithOtp"),
-    verifyOtp: throwNotAvailable("verifyOtp"),
-    reject: throwNotAvailable("reject"),
-});
+import { validateAPIKey, type UIConfig } from "@crossmint/common-sdk-base";
+import {
+    CrossmintWalletUIBaseProvider,
+    type UIRenderProps,
+    type CreateOnLogin,
+    useCrossmint,
+} from "@crossmint/client-sdk-react-base";
+import { EmailSignersDialog } from "@/components/signers/EmailSignersDialog";
+import { PhoneSignersDialog } from "@/components/signers/PhoneSignersDialog";
 
 export interface CrossmintWalletProviderProps {
     children: ReactNode;
     createOnLogin?: CreateOnLogin;
+    appearance?: UIConfig;
+    /** When true (default), no UI is rendered and signing flows must be handled manually. When false, built-in UI components are rendered. */
+    headlessSigningFlow?: boolean;
     callbacks?: {
         onWalletCreationStart?: () => Promise;
         onTransactionStart?: () => Promise;
     };
 }
 
-export function CrossmintWalletProvider({ children, createOnLogin, callbacks }: CrossmintWalletProviderProps) {
+function CrossmintWalletProviderInternal({
+    children,
+    createOnLogin,
+    appearance,
+    headlessSigningFlow = true,
+    callbacks,
+}: CrossmintWalletProviderProps) {
     const { crossmint } = useCrossmint("CrossmintWalletProvider must be used within CrossmintProvider");
     const { apiKey, appId } = crossmint;
 
@@ -56,16 +52,8 @@ export function CrossmintWalletProvider({ children, createOnLogin, callbacks }:
         null
     );
 
-    // Use useState only for needsAuth since it needs to trigger re-renders
-    const [needsAuth, setNeedsAuth] = useState(false);
-
     const [needsWebView, setNeedsWebView] = useState(false);
 
-    // Keep functions as refs to avoid unnecessary re-renders
-    const sendEmailWithOtpRef = useRef<() => Promise>(throwNotAvailable("sendEmailWithOtp"));
-    const verifyOtpRef = useRef<(otp: string) => Promise>(throwNotAvailable("verifyOtp"));
-    const rejectRef = useRef<(error?: Error) => void>(throwNotAvailable("reject"));
-
     const secureGlobals = useMemo(() => {
         if (appId != null) {
             return { crossmintAppId: appId };
@@ -75,7 +63,7 @@ export function CrossmintWalletProvider({ children, createOnLogin, callbacks }:
 
     useEffect(() => {
         if (webviewRef.current != null && webViewParentRef.current == null) {
-            webViewParentRef.current = new WebViewParent(webviewRef, {
+            webViewParentRef.current = new WebViewParent(webviewRef as RefObject, {
                 incomingEvents: signerOutboundEvents,
                 outgoingEvents: signerInboundEvents,
             });
@@ -114,7 +102,7 @@ export function CrossmintWalletProvider({ children, createOnLogin, callbacks }:
                             return argStr;
                         }
                         return JSON.parse(argStr);
-                    } catch (e) {
+                    } catch {
                         return argStr;
                     }
                 });
@@ -138,7 +126,7 @@ export function CrossmintWalletProvider({ children, createOnLogin, callbacks }:
                 }
                 return;
             }
-        } catch (_) {}
+        } catch {}
 
         parent.handleMessage(event);
     }, []);
@@ -150,6 +138,15 @@ export function CrossmintWalletProvider({ children, createOnLogin, callbacks }:
         return webViewParentRef.current;
     };
 
+    const renderNativeUI = ({ emailSignerProps, phoneSignerProps }: UIRenderProps) => {
+        return (
+            <>
+                
+                
+            >
+        );
+    };
+
     const initializeWebView = async () => {
         setNeedsWebView(true);
         let attempts = 0;
@@ -164,39 +161,18 @@ export function CrossmintWalletProvider({ children, createOnLogin, callbacks }:
         }
     };
 
-    const onAuthRequired = async (
-        needsAuth: boolean,
-        sendEmailWithOtp: () => Promise,
-        verifyOtp: (otp: string) => Promise,
-        reject: () => void
-    ) => {
-        setNeedsAuth(needsAuth);
-        sendEmailWithOtpRef.current = sendEmailWithOtp;
-        verifyOtpRef.current = verifyOtp;
-        rejectRef.current = reject;
-    };
-
-    const authContextValue = useMemo(
-        () => ({
-            needsAuth,
-            sendEmailWithOtp: sendEmailWithOtpRef.current,
-            verifyOtp: verifyOtpRef.current,
-            reject: rejectRef.current,
-        }),
-        [needsAuth]
-    );
-
     return (
-        
-            
-                {children}
-            
+            {children}
+
             {needsWebView && (
                 
                 
             )}
-        
+        
     );
 }
+
+export function CrossmintWalletProvider(props: CrossmintWalletProviderProps) {
+    return ;
+}
diff --git a/packages/client/ui/react-native/src/styles/index.ts b/packages/client/ui/react-native/src/styles/index.ts
new file mode 100644
index 000000000..3797aeae0
--- /dev/null
+++ b/packages/client/ui/react-native/src/styles/index.ts
@@ -0,0 +1 @@
+export * from "./theme";
diff --git a/packages/client/ui/react-native/src/styles/theme.ts b/packages/client/ui/react-native/src/styles/theme.ts
new file mode 100644
index 000000000..3924ff464
--- /dev/null
+++ b/packages/client/ui/react-native/src/styles/theme.ts
@@ -0,0 +1,11 @@
+export const theme = {
+    "cm-text-primary": "#00150D",
+    "cm-text-secondary": "#67797F",
+    "cm-background-primary": "#FFFFFF",
+    "cm-muted-primary": "#F0F2F4",
+    "cm-hover": "#E9ECF0",
+    "cm-border": "#D9D9D9",
+    "cm-link": "#1A74E9",
+    "cm-accent": "#4CAF50", // old 04AA6D
+    "cm-danger": "#f44336",
+} as const;
diff --git a/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.test.tsx b/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.test.tsx
index bcca30116..f775671a5 100644
--- a/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.test.tsx
+++ b/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.test.tsx
@@ -7,7 +7,7 @@ vi.mock("@crossmint/client-sdk-react-base", () => ({
     useCrossmint: vi.fn(() => ({
         experimental_customAuth: undefined,
     })),
-    CrossmintWalletBaseProvider: ({ children }: { children: React.ReactNode }) => (
+    CrossmintWalletUIBaseProvider: ({ children }: { children: React.ReactNode }) => (
         {children}
     ),
 }));
diff --git a/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx b/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx
index f1f8472b3..43a805ac6 100644
--- a/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx
+++ b/packages/client/ui/react-ui/src/providers/CrossmintWalletProvider.tsx
@@ -1,30 +1,16 @@
-import { type ReactNode, useState, useCallback, useRef } from "react";
+import type { ReactNode } from "react";
 import type { UIConfig } from "@crossmint/common-sdk-base";
-import { CrossmintWalletBaseProvider, useCrossmint, type CreateOnLogin } from "@crossmint/client-sdk-react-base";
+import {
+    CrossmintWalletUIBaseProvider,
+    type UIRenderProps,
+    type CreateOnLogin,
+} from "@crossmint/client-sdk-react-base";
 
 import { PasskeyPrompt } from "@/components/auth/PasskeyPrompt";
 import { EmailSignersDialog } from "@/components/signers/EmailSignersDialog";
 import { PhoneSignersDialog } from "@/components/signers/PhoneSignersDialog";
 
-const throwNotAvailable = (functionName: string) => () => {
-    throw new Error(`${functionName} is not available. Make sure you're using an email or phone signer wallet.`);
-};
-
-type ValidPasskeyPromptType =
-    | "create-wallet"
-    | "transaction"
-    | "not-supported"
-    | "create-wallet-error"
-    | "transaction-error";
-
-type PasskeyPromptState = {
-    open: boolean;
-    type?: ValidPasskeyPromptType;
-    primaryActionOnClick?: () => void;
-    secondaryActionOnClick?: () => void;
-};
-
-type CrossmintWalletProviderProps = {
+export interface CrossmintWalletProviderProps {
     children: ReactNode;
     showPasskeyHelpers?: boolean;
     appearance?: UIConfig;
@@ -33,7 +19,17 @@ type CrossmintWalletProviderProps = {
         onWalletCreationStart?: () => Promise;
         onTransactionStart?: () => Promise;
     };
-};
+}
+
+function renderWebUI({ emailSignerProps, phoneSignerProps, passkeyPromptProps }: UIRenderProps) {
+    return (
+        <>
+            
+            
+            {passkeyPromptProps != null && }
+        >
+    );
+}
 
 export function CrossmintWalletProvider({
     children,
@@ -42,164 +38,15 @@ export function CrossmintWalletProvider({
     createOnLogin,
     callbacks,
 }: CrossmintWalletProviderProps) {
-    const { experimental_customAuth } = useCrossmint();
-    const [passkeyPromptState, setPasskeyPromptState] = useState({ open: false });
-
-    // Email signer state (for main wallet authentication)
-    const [emailSignerDialogOpen, setEmailSignerDialogOpen] = useState(false);
-    const [emailSignerDialogStep, setEmailSignerDialogStep] = useState<"initial" | "otp">("initial");
-
-    // Phone signer state (for TEE handshake)
-    const [phoneSignerDialogOpen, setPhoneSignerDialogOpen] = useState(false);
-    const [phoneSignerDialogStep, setPhoneSignerDialogStep] = useState<"initial" | "otp">("initial");
-
-    const needsAuthRef = useRef(false);
-
-    // Email signer refs (for main wallet authentication)
-    const sendEmailWithOtpRef = useRef<() => Promise>(throwNotAvailable("sendEmailWithOtp"));
-    const verifyOtpRef = useRef<(otp: string) => Promise>(throwNotAvailable("verifyOtp"));
-
-    // Phone signer refs (for TEE handshake)
-    const sendPhoneWithOtpRef = useRef<() => Promise>(throwNotAvailable("sendPhoneWithOtp"));
-    const verifyPhoneOtpRef = useRef<(otp: string) => Promise>(throwNotAvailable("verifyPhoneOtp"));
-
-    const rejectRef = useRef<(error: Error) => void>(throwNotAvailable("reject"));
-    const phoneNumber =
-        createOnLogin?.signer.type === "phone" && createOnLogin?.signer.phone != null
-            ? createOnLogin.signer.phone
-            : experimental_customAuth?.phone;
-    const email =
-        createOnLogin?.signer.type === "email" && createOnLogin?.signer.email != null
-            ? createOnLogin.signer.email
-            : experimental_customAuth?.email;
-
-    const createPasskeyPrompt = useCallback(
-        (type: ValidPasskeyPromptType) => () =>
-            new Promise((resolve) => {
-                if (!showPasskeyHelpers) {
-                    resolve();
-                    return;
-                }
-                setPasskeyPromptState({
-                    type,
-                    open: true,
-                    primaryActionOnClick: () => {
-                        setPasskeyPromptState({ open: false });
-                        resolve();
-                    },
-                    secondaryActionOnClick: () => {
-                        setPasskeyPromptState({ open: false });
-                        resolve();
-                    },
-                });
-            }),
-        [showPasskeyHelpers]
-    );
-
-    const emailsigners_handleSendEmailOTP = async () => {
-        try {
-            await sendEmailWithOtpRef.current();
-            setEmailSignerDialogStep("otp");
-        } catch (error) {
-            console.error("Failed to send email OTP", error);
-            rejectRef.current(new Error("Failed to send email OTP"));
-        }
-    };
-
-    const emailsigners_handleOTPSubmit = async (otp: string) => {
-        try {
-            await verifyOtpRef.current(otp);
-            setEmailSignerDialogOpen(false);
-        } catch (error) {
-            console.error("Failed to verify OTP", error);
-            rejectRef.current(new Error("Failed to verify OTP"));
-        }
-    };
-
-    // Phone authentication handlers (for TEE handshake)
-    const phonesigners_handleSendPhoneOTP = async () => {
-        try {
-            await sendPhoneWithOtpRef.current();
-            setPhoneSignerDialogStep("otp");
-        } catch (error) {
-            console.error("Failed to send phone OTP", error);
-            rejectRef.current(new Error("Failed to send phone OTP"));
-        }
-    };
-
-    const phonesigners_handleOTPSubmit = async (otp: string) => {
-        try {
-            await verifyPhoneOtpRef.current(otp);
-            setPhoneSignerDialogOpen(false);
-        } catch (error) {
-            console.error("Failed to verify phone OTP", error);
-            rejectRef.current(new Error("Failed to verify phone OTP"));
-        }
-    };
-
-    const getCallbacks = () => {
-        let onWalletCreationStart = callbacks?.onWalletCreationStart;
-        let onTransactionStart = callbacks?.onTransactionStart;
-
-        if (createOnLogin?.signer.type === "passkey" && showPasskeyHelpers) {
-            onWalletCreationStart = createPasskeyPrompt("create-wallet");
-            onTransactionStart = createPasskeyPrompt("transaction");
-        }
-
-        return { onWalletCreationStart, onTransactionStart };
-    };
-
-    const onAuthRequired = async (
-        needsAuth: boolean,
-        sendMessageWithOtp: () => Promise,
-        verifyOtp: (otp: string) => Promise,
-        reject: () => void
-    ) => {
-        // Check if we're dealing with a phone signer
-        if (createOnLogin?.signer.type === "phone" && createOnLogin.signer.phone) {
-            setPhoneSignerDialogOpen(needsAuth);
-            sendPhoneWithOtpRef.current = sendMessageWithOtp;
-            verifyPhoneOtpRef.current = verifyOtp;
-        } else {
-            // Default email signer behavior
-            setEmailSignerDialogOpen(needsAuth);
-            sendEmailWithOtpRef.current = sendMessageWithOtp;
-            verifyOtpRef.current = verifyOtp;
-        }
-        needsAuthRef.current = needsAuth;
-        rejectRef.current = reject;
-    };
-
     return (
-        
             {children}
-            
-            
-            
-        
+        
     );
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b72e1e91a..d415f74b6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -712,9 +712,15 @@ importers:
       bs58:
         specifier: ^5.0.0
         version: 5.0.0
+      lucide-react-native:
+        specifier: 0.548.0
+        version: 0.548.0(react-native-svg@15.14.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.10)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.10)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(react@19.1.0)
       mitt:
         specifier: 3.0.1
         version: 3.0.1
+      react-native-svg:
+        specifier: 15.14.0
+        version: 15.14.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.10)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(react@19.1.0)
       zod:
         specifier: 3.22.4
         version: 3.22.4
@@ -6634,6 +6640,9 @@ packages:
     resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
     engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
 
+  boolbase@1.0.0:
+    resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
   borsh@0.7.0:
     resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==}
 
@@ -7125,9 +7134,16 @@ packages:
   css-jss@10.10.0:
     resolution: {integrity: sha512-YyMIS/LsSKEGXEaVJdjonWe18p4vXLo8CMA4FrW/kcaEyqdIGKCFXao31gbJddXEdIxSXFFURWrenBJPlKTgAA==}
 
+  css-select@5.2.2:
+    resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==}
+
   css-to-react-native@3.2.0:
     resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==}
 
+  css-tree@1.1.3:
+    resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==}
+    engines: {node: '>=8.0.0'}
+
   css-tree@2.3.1:
     resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
     engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
@@ -7415,11 +7431,24 @@ packages:
   dom-helpers@5.2.1:
     resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
 
+  dom-serializer@2.0.0:
+    resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
+  domelementtype@2.3.0:
+    resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
   domexception@4.0.0:
     resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==}
     engines: {node: '>=12'}
     deprecated: Use your platform's native DOMException instead
 
+  domhandler@5.0.3:
+    resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+    engines: {node: '>= 4'}
+
+  domutils@3.2.2:
+    resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+
   dotenv-expand@11.0.7:
     resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==}
     engines: {node: '>=12'}
@@ -9599,6 +9628,13 @@ packages:
     resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
     engines: {node: '>=12'}
 
+  lucide-react-native@0.548.0:
+    resolution: {integrity: sha512-me46HEAZsJJLH3/5FVBOcr3NFkwHZSsaEi2MSlbyGvqx4aVQA8XNDAepYqCleTWpOz+iM3A0J8VAZXg3um810Q==}
+    peerDependencies:
+      react: 19.1.0
+      react-native: 0.81.4
+      react-native-svg: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0
+
   lucide-react@0.419.0:
     resolution: {integrity: sha512-YkOHuc1uGH2A4G0NRZyeCW6mMFGb8z3amep0fARuKIri68nveAT5C8OuXOPJXpb/iIgSfsjdMjjII7bnEtGkvw==}
     peerDependencies:
@@ -9694,6 +9730,9 @@ packages:
   mdast-util-to-string@3.2.0:
     resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==}
 
+  mdn-data@2.0.14:
+    resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
+
   mdn-data@2.0.30:
     resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
 
@@ -10271,6 +10310,9 @@ packages:
     resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
 
+  nth-check@2.1.1:
+    resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
   nullthrows@1.1.1:
     resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==}
 
@@ -11165,6 +11207,12 @@ packages:
       react: 19.1.0
       react-native: 0.81.4
 
+  react-native-svg@15.14.0:
+    resolution: {integrity: sha512-B3gYc7WztcOT4N54AtUutbe0Nuqqh/nkresY0fAXzUHYLsWuIu/yGiCCD3DKfAs6GLv5LFtWTu7N333Q+e3bkg==}
+    peerDependencies:
+      react: 19.1.0
+      react-native: 0.81.4
+
   react-native-web@0.21.2:
     resolution: {integrity: sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==}
     peerDependencies:
@@ -24027,6 +24075,8 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  boolbase@1.0.0: {}
+
   borsh@0.7.0:
     dependencies:
       bn.js: 5.2.2
@@ -24559,12 +24609,25 @@ snapshots:
       jss: 10.10.0
       jss-preset-default: 10.10.0
 
+  css-select@5.2.2:
+    dependencies:
+      boolbase: 1.0.0
+      css-what: 6.2.2
+      domhandler: 5.0.3
+      domutils: 3.2.2
+      nth-check: 2.1.1
+
   css-to-react-native@3.2.0:
     dependencies:
       camelize: 1.0.1
       css-color-keywords: 1.0.0
       postcss-value-parser: 4.2.0
 
+  css-tree@1.1.3:
+    dependencies:
+      mdn-data: 2.0.14
+      source-map: 0.6.1
+
   css-tree@2.3.1:
     dependencies:
       mdn-data: 2.0.30
@@ -24807,10 +24870,28 @@ snapshots:
       '@babel/runtime': 7.28.4
       csstype: 3.1.3
 
+  dom-serializer@2.0.0:
+    dependencies:
+      domelementtype: 2.3.0
+      domhandler: 5.0.3
+      entities: 4.5.0
+
+  domelementtype@2.3.0: {}
+
   domexception@4.0.0:
     dependencies:
       webidl-conversions: 7.0.0
 
+  domhandler@5.0.3:
+    dependencies:
+      domelementtype: 2.3.0
+
+  domutils@3.2.2:
+    dependencies:
+      dom-serializer: 2.0.0
+      domelementtype: 2.3.0
+      domhandler: 5.0.3
+
   dotenv-expand@11.0.7:
     dependencies:
       dotenv: 16.4.5
@@ -27749,6 +27830,12 @@ snapshots:
 
   lru-cache@7.18.3: {}
 
+  lucide-react-native@0.548.0(react-native-svg@15.14.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.10)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.10)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(react@19.1.0):
+    dependencies:
+      react: 19.1.0
+      react-native: 0.81.4(@babel/core@7.28.4)(@types/react@19.1.10)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10)
+      react-native-svg: 15.14.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.10)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(react@19.1.0)
+
   lucide-react@0.419.0(react@19.1.0):
     dependencies:
       react: 19.1.0
@@ -27923,6 +28010,8 @@ snapshots:
     dependencies:
       '@types/mdast': 3.0.15
 
+  mdn-data@2.0.14: {}
+
   mdn-data@2.0.30: {}
 
   mdurl@2.0.0: {}
@@ -28833,6 +28922,10 @@ snapshots:
     dependencies:
       path-key: 4.0.0
 
+  nth-check@2.1.1:
+    dependencies:
+      boolbase: 1.0.0
+
   nullthrows@1.1.1: {}
 
   nwsapi@2.2.22: {}
@@ -29925,6 +30018,14 @@ snapshots:
       react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.10)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(react@19.1.0)
       warn-once: 0.1.1
 
+  react-native-svg@15.14.0(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.10)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10))(react@19.1.0):
+    dependencies:
+      css-select: 5.2.2
+      css-tree: 1.1.3
+      react: 19.1.0
+      react-native: 0.81.4(@babel/core@7.28.4)(@types/react@19.1.10)(bufferutil@4.0.9)(react@19.1.0)(utf-8-validate@5.0.10)
+      warn-once: 0.1.1
+
   react-native-web@0.21.2(encoding@0.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
     dependencies:
       '@babel/runtime': 7.28.4