diff --git a/app.json b/app.json
index 6dbed32..76e9b05 100644
--- a/app.json
+++ b/app.json
@@ -2,8 +2,12 @@
"expo": {
"name": "Alby Go",
"slug": "alby-mobile",
- "version": "1.4.2",
- "scheme": ["bitcoin", "lightning", "alby"],
+ "version": "1.5.0",
+ "scheme": [
+ "bitcoin",
+ "lightning",
+ "alby"
+ ],
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
@@ -12,8 +16,16 @@
"resizeMode": "cover",
"backgroundColor": "#0F0C40"
},
- "assetBundlePatterns": ["**/*"],
+ "assetBundlePatterns": [
+ "**/*"
+ ],
"plugins": [
+ [
+ "expo-local-authentication",
+ {
+ "faceIDPermission": "Allow Alby Go to use Face ID."
+ }
+ ],
[
"expo-camera",
{
@@ -49,7 +61,9 @@
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundImage": "./assets/adaptive-icon-bg.png"
},
- "permissions": ["android.permission.CAMERA"]
+ "permissions": [
+ "android.permission.CAMERA"
+ ]
},
"extra": {
"eas": {
@@ -58,4 +72,4 @@
},
"owner": "roland_alby"
}
-}
+}
\ No newline at end of file
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 642c92b..be4d78d 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -21,9 +21,12 @@ import { toastConfig } from "~/components/ToastConfig";
import * as Font from "expo-font";
import { useInfo } from "~/hooks/useInfo";
import { secureStorage } from "~/lib/secureStorage";
-import { hasOnboardedKey } from "~/lib/state/appStore";
+import { hasOnboardedKey, useAppStore } from "~/lib/state/appStore";
+import { usePathname } from "expo-router";
+import { UserInactivityProvider } from "~/context/UserInactivity";
import { PortalHost } from '@rn-primitives/portal';
import { GestureHandlerRootView } from "react-native-gesture-handler";
+import { isBiometricSupported } from "~/lib/isBiometricSupported";
const LIGHT_THEME: Theme = {
dark: false,
@@ -50,6 +53,8 @@ export default function RootLayout() {
const { isDarkColorScheme } = useColorScheme();
const [fontsLoaded, setFontsLoaded] = React.useState(false);
const [checkedOnboarding, setCheckedOnboarding] = React.useState(false);
+ const isUnlocked = useAppStore((store) => store.unlocked);
+ const pathname = usePathname();
useConnectionChecker();
const rootNavigationState = useRootNavigationState();
@@ -65,7 +70,6 @@ export default function RootLayout() {
};
async function loadFonts() {
-
await Font.loadAsync({
OpenRunde: require("./../assets/fonts/OpenRunde-Regular.otf"),
"OpenRunde-Medium": require("./../assets/fonts/OpenRunde-Medium.otf"),
@@ -76,12 +80,20 @@ export default function RootLayout() {
setFontsLoaded(true);
}
+ async function checkBiometricStatus() {
+ const isSupported = await isBiometricSupported()
+ if (!isSupported) {
+ useAppStore.getState().setSecurityEnabled(false);
+ }
+ }
+
React.useEffect(() => {
const init = async () => {
try {
await Promise.all([
checkOnboardingStatus(),
loadFonts(),
+ checkBiometricStatus(),
]);
}
finally {
@@ -90,9 +102,16 @@ export default function RootLayout() {
};
init();
-
}, [hasNavigationState]);
+ React.useEffect(() => {
+ if (hasNavigationState && !isUnlocked) {
+ if (pathname !== "/unlock") {
+ router.push("/unlock");
+ }
+ }
+ }, [isUnlocked, hasNavigationState]);
+
if (!fontsLoaded || !checkedOnboarding) {
return null;
}
@@ -104,7 +123,9 @@ export default function RootLayout() {
-
+
+
+
diff --git a/app/settings/security.js b/app/settings/security.js
new file mode 100644
index 0000000..1dca538
--- /dev/null
+++ b/app/settings/security.js
@@ -0,0 +1,5 @@
+import { Security } from "../../pages/settings/Security";
+
+export default function Page() {
+ return ;
+}
diff --git a/app/unlock.js b/app/unlock.js
new file mode 100644
index 0000000..b17a77d
--- /dev/null
+++ b/app/unlock.js
@@ -0,0 +1,5 @@
+import { Unlock } from "../pages/Unlock";
+
+export default function Page() {
+ return ;
+}
diff --git a/components/DualCurrencyInput.tsx b/components/DualCurrencyInput.tsx
index b18958d..bebedc5 100644
--- a/components/DualCurrencyInput.tsx
+++ b/components/DualCurrencyInput.tsx
@@ -11,12 +11,14 @@ type DualCurrencyInputProps = {
amount: string;
setAmount(amount: string): void;
autoFocus?: boolean;
+ readOnly?: boolean;
};
export function DualCurrencyInput({
amount,
setAmount,
autoFocus = false,
+ readOnly = false,
}: DualCurrencyInputProps) {
const getFiatAmount = useGetFiatAmount();
const getSatsAmount = useGetSatsAmount();
@@ -58,6 +60,7 @@ export function DualCurrencyInput({
style={styles.amountInput}
autoFocus={autoFocus}
returnKeyType="done"
+ readOnly={readOnly}
// aria-errormessage="inputError"
/>
diff --git a/components/Icons.tsx b/components/Icons.tsx
index a40dbac..51d14a2 100644
--- a/components/Icons.tsx
+++ b/components/Icons.tsx
@@ -34,8 +34,10 @@ import {
CameraOff,
Palette,
Egg,
+ Fingerprint,
HelpCircle,
CircleCheck,
+ TriangleAlert,
} from "lucide-react-native";
import { cssInterop } from "nativewind";
@@ -85,8 +87,10 @@ interopIcon(Power);
interopIcon(CameraOff);
interopIcon(Palette);
interopIcon(Egg);
+interopIcon(Fingerprint);
interopIcon(HelpCircle);
interopIcon(CircleCheck);
+interopIcon(TriangleAlert);
export {
AlertCircle,
@@ -123,6 +127,8 @@ export {
Power,
Palette,
Egg,
+ Fingerprint,
HelpCircle,
CircleCheck,
+ TriangleAlert,
};
diff --git a/components/ui/switch.tsx b/components/ui/switch.tsx
new file mode 100644
index 0000000..951716d
--- /dev/null
+++ b/components/ui/switch.tsx
@@ -0,0 +1,96 @@
+import * as SwitchPrimitives from '@rn-primitives/switch';
+import * as React from 'react';
+import { Platform } from 'react-native';
+import Animated, {
+ interpolateColor,
+ useAnimatedStyle,
+ useDerivedValue,
+ withTiming,
+} from 'react-native-reanimated';
+import { useColorScheme } from '~/lib/useColorScheme';
+import { cn } from '~/lib/utils';
+
+const SwitchWeb = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+
+SwitchWeb.displayName = 'SwitchWeb';
+
+const RGB_COLORS = {
+ light: {
+ primary: 'rgb(255, 224, 112)',
+ input: 'rgb(228, 228, 231)',
+ },
+ dark: {
+ primary: 'rgb(255, 224, 112)',
+ input: 'rgb(228, 228, 231)',
+ },
+} as const;
+
+const SwitchNative = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const { colorScheme } = useColorScheme();
+ const translateX = useDerivedValue(() => (props.checked ? 18 : 0));
+ const animatedRootStyle = useAnimatedStyle(() => {
+ return {
+ backgroundColor: interpolateColor(
+ Number(props.checked),
+ [0, 1],
+ [RGB_COLORS[colorScheme].input, RGB_COLORS[colorScheme].primary]
+ ),
+ };
+ });
+ const animatedThumbStyle = useAnimatedStyle(() => ({
+ transform: [{ translateX: withTiming(translateX.value, { duration: 200 }) }],
+ }));
+ return (
+
+
+
+
+
+
+
+ );
+});
+SwitchNative.displayName = 'SwitchNative';
+
+const Switch = Platform.select({
+ web: SwitchWeb,
+ default: SwitchNative,
+});
+
+export { Switch };
diff --git a/context/UserInactivity.tsx b/context/UserInactivity.tsx
new file mode 100644
index 0000000..2133fd7
--- /dev/null
+++ b/context/UserInactivity.tsx
@@ -0,0 +1,42 @@
+import * as React from "react";
+import { AppState, AppStateStatus, NativeEventSubscription } from 'react-native';
+import { secureStorage } from "~/lib/secureStorage";
+import { INACTIVITY_THRESHOLD } from "~/lib/constants";
+import { lastActiveTimeKey, useAppStore } from "~/lib/state/appStore";
+
+export const UserInactivityProvider = ({ children }: any) => {
+ const [appState, setAppState] = React.useState(AppState.currentState);
+ const isSecurityEnabled = useAppStore((store) => store.isSecurityEnabled);
+
+ const handleAppStateChange = async (nextState: AppStateStatus) => {
+ if (appState === "active" && nextState.match(/inactive|background/)) {
+ const now = Date.now();
+ secureStorage.setItem(lastActiveTimeKey, now.toString());
+ } else if (appState.match(/inactive|background/) && nextState === "active") {
+ const lastActiveTime = secureStorage.getItem(lastActiveTimeKey);
+ if (lastActiveTime) {
+ const timeElapsed = Date.now() - parseInt(lastActiveTime, 10);
+ if (timeElapsed >= INACTIVITY_THRESHOLD) {
+ useAppStore.getState().setUnlocked(false)
+ }
+ }
+ await secureStorage.removeItem(lastActiveTimeKey);
+ }
+ setAppState(nextState);
+ };
+
+ React.useEffect(() => {
+ let subscription: NativeEventSubscription
+ if (isSecurityEnabled) {
+ subscription = AppState.addEventListener("change", handleAppStateChange);
+ }
+
+ return () => {
+ if (subscription) {
+ subscription.remove();
+ }
+ };
+ }, [appState, isSecurityEnabled]);
+
+ return children;
+}
\ No newline at end of file
diff --git a/lib/constants.ts b/lib/constants.ts
index 46c2eae..5cae282 100644
--- a/lib/constants.ts
+++ b/lib/constants.ts
@@ -17,6 +17,8 @@ export const NAV_THEME = {
},
};
+export const INACTIVITY_THRESHOLD = 5 * 60 * 1000;
+
export const CURSOR_COLOR = "hsl(47 100% 72%)";
export const TRANSACTIONS_PAGE_SIZE = 20;
diff --git a/lib/isBiometricSupported.ts b/lib/isBiometricSupported.ts
new file mode 100644
index 0000000..bcbefd4
--- /dev/null
+++ b/lib/isBiometricSupported.ts
@@ -0,0 +1,7 @@
+import * as LocalAuthentication from "expo-local-authentication";
+
+export async function isBiometricSupported() {
+ const compatible = await LocalAuthentication.hasHardwareAsync();
+ const securityLevel = await LocalAuthentication.getEnrolledLevelAsync();
+ return compatible && securityLevel > 0
+}
\ No newline at end of file
diff --git a/lib/state/appStore.ts b/lib/state/appStore.ts
index f5a827d..fa5f151 100644
--- a/lib/state/appStore.ts
+++ b/lib/state/appStore.ts
@@ -4,11 +4,14 @@ import { nwc } from "@getalby/sdk";
import { secureStorage } from "lib/secureStorage";
interface AppState {
+ readonly unlocked: boolean;
readonly nwcClient: NWCClient | undefined;
readonly fiatCurrency: string;
readonly selectedWalletId: number;
readonly wallets: Wallet[];
readonly addressBookEntries: AddressBookEntry[];
+ readonly isSecurityEnabled: boolean;
+ setUnlocked: (unlocked: boolean) => void;
setNWCClient: (nwcClient: NWCClient | undefined) => void;
setNostrWalletConnectUrl(nostrWalletConnectUrl: string): void;
removeNostrWalletConnectUrl(): void;
@@ -16,6 +19,7 @@ interface AppState {
removeCurrentWallet(): void;
setFiatCurrency(fiatCurrency: string): void;
setSelectedWalletId(walletId: number): void;
+ setSecurityEnabled(securityEnabled: boolean): void;
addWallet(wallet: Wallet): void;
addAddressBookEntry(entry: AddressBookEntry): void;
reset(): void;
@@ -26,7 +30,9 @@ const walletKeyPrefix = "wallet";
const addressBookEntryKeyPrefix = "addressBookEntry";
const selectedWalletIdKey = "selectedWalletId";
const fiatCurrencyKey = "fiatCurrency";
+export const isSecurityEnabledKey = "isSecurityEnabled";
export const hasOnboardedKey = "hasOnboarded";
+export const lastActiveTimeKey = "lastActiveTime";
type Wallet = {
name?: string;
@@ -99,7 +105,15 @@ export const useAppStore = create()((set, get) => {
const removeCurrentWallet = () => {
const wallets = [...get().wallets];
if (wallets.length <= 1) {
- // cannot delete last wallet
+ // set to initial wallet status
+ secureStorage.removeItem(hasOnboardedKey);
+ secureStorage.setItem(selectedWalletIdKey, "0");
+ secureStorage.setItem(getWalletKey(0), JSON.stringify({}));
+ set({
+ nwcClient: undefined,
+ selectedWalletId: 0,
+ wallets: [{}],
+ });
return;
}
const selectedWalletId = get().selectedWalletId;
@@ -124,15 +138,23 @@ export const useAppStore = create()((set, get) => {
const initialSelectedWalletId = +(
secureStorage.getItem(selectedWalletIdKey) || "0"
);
+
+ const iSecurityEnabled = secureStorage.getItem(isSecurityEnabledKey) === "true";
+
const initialWallets = loadWallets();
return {
+ unlocked: !iSecurityEnabled,
addressBookEntries: loadAddressBookEntries(),
wallets: initialWallets,
nwcClient: getNWCClient(initialSelectedWalletId),
fiatCurrency: secureStorage.getItem(fiatCurrencyKey) || "",
+ isSecurityEnabled: iSecurityEnabled,
selectedWalletId: initialSelectedWalletId,
updateCurrentWallet,
removeCurrentWallet,
+ setUnlocked: (unlocked) => {
+ set({ unlocked });
+ },
setNWCClient: (nwcClient) => set({ nwcClient }),
removeNostrWalletConnectUrl: () => {
updateCurrentWallet({
@@ -146,6 +168,13 @@ export const useAppStore = create()((set, get) => {
nostrWalletConnectUrl,
});
},
+ setSecurityEnabled: (isEnabled) => {
+ secureStorage.setItem(isSecurityEnabledKey, isEnabled.toString());
+ set({
+ isSecurityEnabled: isEnabled,
+ ...(!isEnabled ? { unlocked: true } : {}),
+ });
+ },
setFiatCurrency: (fiatCurrency) => {
secureStorage.setItem(fiatCurrencyKey, fiatCurrency);
set({ fiatCurrency });
@@ -191,18 +220,27 @@ export const useAppStore = create()((set, get) => {
for (let i = 0; i < get().addressBookEntries.length; i++) {
secureStorage.removeItem(getAddressBookEntryKey(i));
}
- // clear selected wallet ID
- secureStorage.removeItem(selectedWalletIdKey);
+
+ // clear fiat currency
+ secureStorage.removeItem(fiatCurrencyKey);
+
+ // clear security enabled status
+ secureStorage.removeItem(isSecurityEnabledKey);
// clear onboarding status
secureStorage.removeItem(hasOnboardedKey);
+ // set to initial wallet status
+ secureStorage.setItem(selectedWalletIdKey, "0");
+ secureStorage.setItem(getWalletKey(0), JSON.stringify({}));
+
set({
nwcClient: undefined,
fiatCurrency: undefined,
- selectedWalletId: undefined,
- wallets: [],
+ selectedWalletId: 0,
+ wallets: [{}],
addressBookEntries: [],
+ isSecurityEnabled: false,
});
},
};
diff --git a/package.json b/package.json
index bbfe632..ef13127 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "alby-go",
- "version": "1.4.2",
+ "version": "1.5.0",
"main": "expo-router/entry",
"scripts": {
"start": "expo start",
@@ -21,6 +21,7 @@
"@react-native-async-storage/async-storage": "1.23.1",
"@rn-primitives/dialog": "^1.0.3",
"@rn-primitives/portal": "^1.0.3",
+ "@rn-primitives/switch": "^1.0.3",
"bech32": "^2.0.0",
"buffer": "^6.0.3",
"class-variance-authority": "^0.7.0",
@@ -34,6 +35,7 @@
"expo-font": "^12.0.9",
"expo-linear-gradient": "~13.0.2",
"expo-linking": "~6.3.1",
+ "expo-local-authentication": "~14.0.1",
"expo-router": "^3.5.23",
"expo-secure-store": "^13.0.2",
"expo-status-bar": "~1.12.1",
@@ -53,7 +55,7 @@
"react-native-toast-message": "^2.2.0",
"react-native-url-polyfill": "^2.0.0",
"react-native-webview": "13.8.6",
- "react-native-webview-crypto": "^0.0.25",
+ "react-native-webview-crypto": "^0.0.26",
"swr": "^2.2.5",
"tailwind-merge": "^2.3.0",
"text-encoding": "^0.7.0",
diff --git a/pages/Unlock.tsx b/pages/Unlock.tsx
new file mode 100644
index 0000000..d2110b0
--- /dev/null
+++ b/pages/Unlock.tsx
@@ -0,0 +1,56 @@
+import { router, Stack } from "expo-router";
+import React from "react";
+import { View, Image } from "react-native";
+import * as LocalAuthentication from "expo-local-authentication";
+
+import { Button } from "~/components/ui/button";
+import { Text } from "~/components/ui/text";
+import { useAppStore } from "~/lib/state/appStore";
+
+export function Unlock() {
+ const [isUnlocking, setIsUnlocking] = React.useState(false);
+
+ const handleUnlock = async () => {
+ try {
+ setIsUnlocking(true);
+ const biometricAuth = await LocalAuthentication.authenticateAsync({
+ promptMessage: "Unlock Alby Go",
+ });
+ if (biometricAuth.success) {
+ useAppStore.getState().setUnlocked(true);
+ if (router.canGoBack()) {
+ router.back();
+ } else {
+ router.replace("/");
+ }
+ }
+ } finally {
+ setIsUnlocking(false);
+ }
+ };
+
+ React.useEffect(() => {
+ handleUnlock();
+ }, []);
+
+ return (
+
+
+
+
+ Unlock to continue
+
+
+
+ );
+}
diff --git a/pages/receive/Receive.tsx b/pages/receive/Receive.tsx
index 18c7f50..177fd2f 100644
--- a/pages/receive/Receive.tsx
+++ b/pages/receive/Receive.tsx
@@ -101,7 +101,7 @@ export function Receive() {
polling &&
pollCount > 0 &&
receivedTransaction.payment_hash !==
- prevTransaction?.payment_hash
+ prevTransaction?.payment_hash
) {
if (
!invoiceRef.current ||
@@ -176,7 +176,10 @@ export function Receive() {
return (
<>
-
+
{!enterCustomAmount && !invoice && !lightningAddress && (
<>
diff --git a/pages/send/LNURLPay.tsx b/pages/send/LNURLPay.tsx
index c3c3120..bd410a3 100644
--- a/pages/send/LNURLPay.tsx
+++ b/pages/send/LNURLPay.tsx
@@ -1,6 +1,6 @@
import Screen from "~/components/Screen";
import { router, useLocalSearchParams } from "expo-router";
-import React from "react";
+import React, { useEffect } from "react";
import { View } from "react-native";
import { Button } from "~/components/ui/button";
import { Text } from "~/components/ui/text";
@@ -21,6 +21,15 @@ export function LNURLPay() {
const [isLoading, setLoading] = React.useState(false);
const [amount, setAmount] = React.useState("");
const [comment, setComment] = React.useState("");
+ const [isAmountReadOnly, setAmountReadOnly] = React.useState(false);
+
+ useEffect(() => {
+ // Handle fixed amount LNURLs
+ if (lnurlDetails.minSendable === lnurlDetails.maxSendable) {
+ setAmount((lnurlDetails.minSendable / 1000).toString());
+ setAmountReadOnly(true);
+ }
+ }, [lnurlDetails.minSendable, lnurlDetails.maxSendable]);
async function requestInvoice() {
setLoading(true);
@@ -53,20 +62,24 @@ export function LNURLPay() {
-
-
- Comment
-
-
-
+ {lnurlDetails.commentAllowed &&
+
+
+ Comment
+
+
+
+ }
To
diff --git a/pages/send/PaymentSuccess.tsx b/pages/send/PaymentSuccess.tsx
index d5eb4cc..bda1184 100644
--- a/pages/send/PaymentSuccess.tsx
+++ b/pages/send/PaymentSuccess.tsx
@@ -19,7 +19,7 @@ export function PaymentSuccess() {
-
+
diff --git a/pages/send/Send.tsx b/pages/send/Send.tsx
index 9f24320..923ad40 100644
--- a/pages/send/Send.tsx
+++ b/pages/send/Send.tsx
@@ -83,13 +83,29 @@ export function Send() {
throw new Error("LNURL tag " + lnurlDetails.tag + " not supported");
}
- router.replace({
- pathname: "/send/lnurl-pay",
- params: {
- lnurlDetailsJSON: JSON.stringify(lnurlDetails),
- originalText,
- },
- });
+ // Handle fixed amount LNURLs
+ if (lnurlDetails.minSendable === lnurlDetails.maxSendable && !lnurlDetails.commentAllowed) {
+ try {
+ const callback = new URL(lnurlDetails.callback);
+ callback.searchParams.append("amount", (lnurlDetails.minSendable).toString());
+ const lnurlPayInfo = await lnurl.getPayRequest(callback.toString());
+ router.push({
+ pathname: "/send/confirm",
+ params: { invoice: lnurlPayInfo.pr, originalText },
+ });
+ } catch (error) {
+ console.error(error);
+ errorToast(error);
+ }
+ } else {
+ router.replace({
+ pathname: "/send/lnurl-pay",
+ params: {
+ lnurlDetailsJSON: JSON.stringify(lnurlDetails),
+ originalText,
+ },
+ });
+ }
} else {
// Check if this is a valid invoice
new Invoice({
diff --git a/pages/settings/Security.tsx b/pages/settings/Security.tsx
new file mode 100644
index 0000000..ea43809
--- /dev/null
+++ b/pages/settings/Security.tsx
@@ -0,0 +1,78 @@
+import React from "react";
+import { Text, View } from "react-native";
+import { TriangleAlert } from "~/components/Icons";
+import Loading from "~/components/Loading";
+import Screen from "~/components/Screen";
+import {
+ Card,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "~/components/ui/card";
+import { Label } from "~/components/ui/label";
+import { Switch } from "~/components/ui/switch";
+import { isBiometricSupported } from "~/lib/isBiometricSupported";
+import { useAppStore } from "~/lib/state/appStore";
+import { cn } from "~/lib/utils";
+
+export function Security() {
+ const [isSupported, setIsSupported] = React.useState(null);
+ const isEnabled = useAppStore((store) => store.isSecurityEnabled);
+
+ React.useEffect(() => {
+ async function checkBiometricSupport() {
+ const supported = await isBiometricSupported();
+ setIsSupported(supported);
+ }
+ checkBiometricSupport();
+ }, []);
+
+ return (
+
+
+ {isSupported === null ? (
+
+
+
+ ) : (
+ <>
+ {!isSupported && (
+
+
+
+
+ Setup Device Security
+
+
+ To protect your wallet, please set up a phone lock in your device settings first.
+
+
+
+ )}
+
+
+
+ {
+ useAppStore.getState().setSecurityEnabled(!isEnabled);
+ }}
+ nativeID="security"
+ />
+
+
+ >
+ )}
+
+ );
+}
diff --git a/pages/settings/Settings.tsx b/pages/settings/Settings.tsx
index ca78a08..67ca840 100644
--- a/pages/settings/Settings.tsx
+++ b/pages/settings/Settings.tsx
@@ -1,6 +1,6 @@
import { Link, router } from "expo-router";
import { Alert, TouchableOpacity, View } from "react-native";
-import { Bitcoin, Egg, Palette, Power, Wallet2 } from "~/components/Icons";
+import { Bitcoin, Egg, Fingerprint, Palette, Power, Wallet2 } from "~/components/Icons";
import { DEFAULT_CURRENCY, DEFAULT_WALLET_NAME } from "~/lib/constants";
import { useAppStore } from "~/lib/state/appStore";
@@ -43,6 +43,15 @@ export function Settings() {
+
+
+
+
+ Security
+
+
+
+
{
- router.back();
+ if (useAppStore.getState().wallets.length == 1) {
+ router.dismissAll();
+ router.replace("/onboarding");
+ } else {
+ router.back();
+ }
useAppStore.getState().removeCurrentWallet();
},
},
diff --git a/yarn.lock b/yarn.lock
index 0244912..c26a46d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1604,6 +1604,19 @@
dependencies:
"@radix-ui/react-compose-refs" "1.1.0"
+"@radix-ui/react-switch@^1.0.3":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.1.0.tgz#fcf8e778500f1d60d4b2bec2fc3fad77a7c118e3"
+ integrity sha512-OBzy5WAj641k0AOSpKQtreDMe+isX0MQJ1IVyF03ucdF3DunOnROVrjWs8zsXUxC3zfZ6JL9HFVCUlMghz9dJw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.0"
+ "@radix-ui/react-compose-refs" "1.1.0"
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-use-controllable-state" "1.1.0"
+ "@radix-ui/react-use-previous" "1.1.0"
+ "@radix-ui/react-use-size" "1.1.0"
+
"@radix-ui/react-use-callback-ref@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1"
@@ -1628,6 +1641,18 @@
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27"
integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==
+"@radix-ui/react-use-previous@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c"
+ integrity sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==
+
+"@radix-ui/react-use-size@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz#b4dba7fbd3882ee09e8d2a44a3eed3a7e555246b"
+ integrity sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==
+ dependencies:
+ "@radix-ui/react-use-layout-effect" "1.1.0"
+
"@react-native-async-storage/async-storage@1.23.1":
version "1.23.1"
resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.23.1.tgz#cad3cd4fab7dacfe9838dce6ecb352f79150c883"
@@ -2127,6 +2152,15 @@
resolved "https://registry.yarnpkg.com/@rn-primitives/slot/-/slot-1.0.3.tgz#878b488dd7844703eb0b4dbc51c239e1bdb0ca11"
integrity sha512-sSDYwD2upLQUOva2cmNfaJqZlN+o0E/wa8uXEDEIYj6MJsG/O0k1t0jGXMqLP8BsQ3ZaTtUbqnG9eW/VQFOmdQ==
+"@rn-primitives/switch@^1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@rn-primitives/switch/-/switch-1.0.3.tgz#8e26b4223226ec9aa125b4e875b0f99eaaf82c51"
+ integrity sha512-bzgRcdBw8myXeHerGxZw1bhhP7afqkj0N0nzcWmK0o9Epr70b++X1LkIFe0//BGsTkrulb02aJEkVg6UOgez3g==
+ dependencies:
+ "@radix-ui/react-switch" "^1.0.3"
+ "@rn-primitives/slot" "1.0.3"
+ "@rn-primitives/types" "1.0.3"
+
"@rn-primitives/types@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@rn-primitives/types/-/types-1.0.3.tgz#1d0b084663859a889e5b7359eb2afe62ff311adb"
@@ -3845,6 +3879,13 @@ expo-linking@~6.3.1:
expo-constants "~16.0.0"
invariant "^2.2.4"
+expo-local-authentication@~14.0.1:
+ version "14.0.1"
+ resolved "https://registry.yarnpkg.com/expo-local-authentication/-/expo-local-authentication-14.0.1.tgz#0cad7f419d4694e1c205f589cdf355f2f2b3a2b4"
+ integrity sha512-kAwUD1wEqj1fhwQgIHlP4H/JV9AcX+NO3BJwhPM2HuCFS0kgx2wvcHisnKBSTRyl8u5Jt4odzMyQkDJystwUTg==
+ dependencies:
+ invariant "^2.2.4"
+
expo-modules-autolinking@1.11.2:
version "1.11.2"
resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-1.11.2.tgz#98245eb66f157bbfebebd2d576256ea7c683c605"
@@ -6577,10 +6618,10 @@ react-native-url-polyfill@^2.0.0:
dependencies:
whatwg-url-without-unicode "8.0.0-3"
-react-native-webview-crypto@^0.0.25:
- version "0.0.25"
- resolved "https://registry.yarnpkg.com/react-native-webview-crypto/-/react-native-webview-crypto-0.0.25.tgz#c35506e1f092f7633db684f388f2b449667a05a2"
- integrity sha512-H1kn5FFk0tBq5JDpkopyonAQTFEDAGoVJG+9Ip84jx4QmHmh5hxaJ5PkOXsMeNb2wHnwuvsg5p3krCOYNf20+A==
+react-native-webview-crypto@^0.0.26:
+ version "0.0.26"
+ resolved "https://registry.yarnpkg.com/react-native-webview-crypto/-/react-native-webview-crypto-0.0.26.tgz#bce360876ed3367e677cb5cfa88564ca892ba72f"
+ integrity sha512-RshjQDik60LOhh7Q+SKIsXjnCgCIBZqZOB+v4MWa7l+0uAEyeZyMkWKL0xJRzWfTQ9vnLu4nHyn6qjEEi0uHnA==
dependencies:
encode-utf8 "^1.0.2"
fast-base64-encode "^1.0.0"