diff --git a/android/App.js b/android/App.js index 42ae0d9..7f444e2 100644 --- a/android/App.js +++ b/android/App.js @@ -18,7 +18,6 @@ import { Text, View, TouchableOpacity, - Alert, ScrollView, FlatList, Image, @@ -55,6 +54,10 @@ import slip10 from "micro-key-producer/slip10.js"; import { randomBytes, secretbox } from "tweetnacl"; import bs58 from "bs58"; import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { + SafeAreaProvider, + useSafeAreaInsets, +} from "react-native-safe-area-context"; import BottomSheet, { BottomSheetBackdrop, BottomSheetView, @@ -286,6 +289,9 @@ const NETWORKS = [ const apolloClient = createApolloClient("backpack-android", "1.0.0"); function AppContent() { + // Get safe area insets for proper spacing on devices with notches/gesture bars + const insets = useSafeAreaInsets(); + // Authentication states const [authState, setAuthState] = useState("loading"); // 'loading', 'setup', 'locked', 'unlocked' const [password, setPassword] = useState(null); @@ -662,23 +668,12 @@ function AppContent() { const netInfoState = await NetInfo.fetch(); if (!netInfoState.isConnected || !netInfoState.isInternetReachable) { - Alert.alert( - "No Network Connection", - "Please open Settings and connect to WiFi to use this app.", - [ - { - text: "Open Settings", - onPress: () => { - if (Platform.OS === "android") { - Linking.openSettings(); - } else { - Linking.openURL("app-settings:"); - } - }, - }, - { text: "Cancel", style: "cancel" }, - ] - ); + Toast.show({ + type: "error", + text1: "No Network Connection", + text2: "Please connect to WiFi to use this app.", + position: "bottom", + }); } setHasCheckedNetwork(true); } catch (error) { @@ -1079,57 +1074,46 @@ function AppContent() { console.log("Full wallet object:", JSON.stringify(wallet, null, 2)); console.log("=== END WALLET INFO ==="); - Alert.alert( - "Delete Wallet", - `Are you sure you want to delete "${wallet.name}"?`, - [ - { - text: "Cancel", - style: "cancel", - onPress: () => { - console.log("Delete cancelled for wallet:", wallet.name); - }, - }, - { - text: "Delete", - style: "destructive", - onPress: async () => { - console.log("Deleting wallet:", wallet.name); - // Remove the wallet from the list - const updatedWallets = wallets.filter((w) => w.id !== wallet.id); - console.log("Wallets after deletion:", updatedWallets.length); - setWallets(updatedWallets); - await deleteWalletMnemonicSecurely(wallet.id); - - // If we deleted the selected wallet, select the first remaining wallet or reset - if (wallet.selected && updatedWallets.length > 0) { - const newSelectedWallet = { - ...updatedWallets[0], - selected: true, - }; - setWallets( - updatedWallets.map((w) => ({ - ...w, - selected: w.id === newSelectedWallet.id, - })) - ); - setSelectedWallet(newSelectedWallet); - } else if (updatedWallets.length === 0) { - // No wallets left, reset to initial state - setSelectedWallet({ - id: 1, - name: "Wallet 1", - address: "Abc1...xyz2", - publicKey: "", - selected: true, - }); - } + // Delete wallet directly + (async () => { + console.log("Deleting wallet:", wallet.name); + // Remove the wallet from the list + const updatedWallets = wallets.filter((w) => w.id !== wallet.id); + console.log("Wallets after deletion:", updatedWallets.length); + setWallets(updatedWallets); + await deleteWalletMnemonicSecurely(wallet.id); + + // If we deleted the selected wallet, select the first remaining wallet or reset + if (wallet.selected && updatedWallets.length > 0) { + const newSelectedWallet = { + ...updatedWallets[0], + selected: true, + }; + setWallets( + updatedWallets.map((w) => ({ + ...w, + selected: w.id === newSelectedWallet.id, + })) + ); + setSelectedWallet(newSelectedWallet); + } else if (updatedWallets.length === 0) { + // No wallets left, reset to initial state + setSelectedWallet({ + id: 1, + name: "Wallet 1", + address: "Abc1...xyz2", + publicKey: "", + selected: true, + }); + } - Alert.alert("Success", "Wallet deleted successfully"); - }, - }, - ] - ); + Toast.show({ + type: "success", + text1: "Success", + text2: "Wallet deleted successfully", + position: "bottom", + }); + })(); }; const openSeedPhraseSheet = useCallback(async () => { @@ -1442,15 +1426,14 @@ function AppContent() { 100 ); - // Open explorer URL after short delay to allow user to see toast + // Show success toast after transaction confirmation setTimeout(() => { - Alert.alert("Transaction Successful", `View on explorer?`, [ - { - text: "View Transaction", - onPress: () => Linking.openURL(explorerUrl), - }, - { text: "Close", style: "cancel" }, - ]); + Toast.show({ + type: "success", + text1: "Transaction Successful", + text2: "Your transaction has been confirmed", + position: "bottom", + }); }, 1000); } @@ -2036,16 +2019,23 @@ function AppContent() { if (importType === "mnemonic") { if (!bip39.validateMnemonic(normalizedMnemonic)) { - Alert.alert("Error", "Invalid recovery phrase"); + Toast.show({ + type: "error", + text1: "Error", + text2: "Invalid recovery phrase", + position: "bottom", + }); return; } const parsedIndex = parseInt(importDerivationIndex, 10); if (Number.isNaN(parsedIndex) || parsedIndex < 0) { - Alert.alert( - "Error", - "Derivation index must be a non-negative number" - ); + Toast.show({ + type: "error", + text1: "Error", + text2: "Derivation index must be a non-negative number", + position: "bottom", + }); return; } @@ -2081,10 +2071,12 @@ function AppContent() { const privateKeyArray = JSON.parse(trimmedKey); keypair = Keypair.fromSecretKey(new Uint8Array(privateKeyArray)); } catch { - Alert.alert( - "Error", - "Invalid private key format. Use bs58 or JSON array format." - ); + Toast.show({ + type: "error", + text1: "Error", + text2: "Invalid private key format. Use bs58 or JSON array format.", + position: "bottom", + }); return; } } @@ -2095,7 +2087,12 @@ function AppContent() { const isDuplicate = wallets.some((w) => w.publicKey === publicKeyStr); if (isDuplicate) { - Alert.alert("Duplicate Wallet", "This wallet has already been added."); + Toast.show({ + type: "error", + text1: "Duplicate Wallet", + text2: "This wallet has already been added.", + position: "bottom", + }); return; } @@ -2131,7 +2128,12 @@ function AppContent() { // Register the wallet with the transaction indexer await registerWalletWithIndexer(publicKeyStr, currentNetwork.providerId); } catch (error) { - Alert.alert("Error", "Failed to import wallet: " + error.message); + Toast.show({ + type: "error", + text1: "Error", + text2: "Failed to import wallet: " + error.message, + position: "bottom", + }); } }; @@ -2154,7 +2156,12 @@ function AppContent() { const isDuplicate = wallets.some((w) => w.publicKey === publicKeyStr); if (isDuplicate) { - Alert.alert("Duplicate Wallet", "This wallet has already been added."); + Toast.show({ + type: "error", + text1: "Duplicate Wallet", + text2: "This wallet has already been added.", + position: "bottom", + }); return; } @@ -2183,7 +2190,12 @@ function AppContent() { setNewMnemonic(""); setShowCreateWalletModal(false); } catch (error) { - Alert.alert("Error", "Failed to create wallet: " + error.message); + Toast.show({ + type: "error", + text1: "Error", + text2: "Failed to create wallet: " + error.message, + position: "bottom", + }); console.error("Wallet creation error:", error); } }; @@ -2212,66 +2224,70 @@ function AppContent() { if (changeSeedPhraseMode === "enter") { // Validate entered seed phrase if (!newSeedPhraseInput.trim()) { - Alert.alert("Error", "Please enter a seed phrase"); + Toast.show({ + type: "error", + text1: "Error", + text2: "Please enter a seed phrase", + position: "bottom", + }); return; } if (!bip39.validateMnemonic(newSeedPhraseInput.trim())) { - Alert.alert( - "Error", - "Invalid seed phrase. Please check and try again." - ); + Toast.show({ + type: "error", + text1: "Error", + text2: "Invalid seed phrase. Please check and try again.", + position: "bottom", + }); return; } seedToUse = newSeedPhraseInput.trim(); } else { // Use generated seed phrase if (!generatedNewSeed) { - Alert.alert("Error", "Please generate a seed phrase first"); + Toast.show({ + type: "error", + text1: "Error", + text2: "Please generate a seed phrase first", + position: "bottom", + }); return; } seedToUse = generatedNewSeed; } - // Warn user about existing wallets - Alert.alert( - "Change Seed Phrase", - "Changing your seed phrase will affect newly created wallets only. Existing wallets will remain unchanged. Do you want to continue?", - [ - { text: "Cancel", style: "cancel" }, - { - text: "Continue", - onPress: async () => { - try { - setMasterSeedPhrase(seedToUse); - await saveMasterSeedPhrase(seedToUse); + // Change seed phrase (affects newly created wallets only) + try { + setMasterSeedPhrase(seedToUse); + await saveMasterSeedPhrase(seedToUse); - // Reset derivation index to 0 for new seed phrase - setWalletDerivationIndex(0); - await saveDerivationIndex(0); + // Reset derivation index to 0 for new seed phrase + setWalletDerivationIndex(0); + await saveDerivationIndex(0); - console.log("Master seed phrase changed successfully"); - ToastAndroid.show( - "Seed phrase changed successfully", - ToastAndroid.SHORT - ); + console.log("Master seed phrase changed successfully"); + Toast.show({ + type: "success", + text1: "Seed Phrase Changed", + text2: "New wallets will use the updated seed phrase", + position: "bottom", + }); - // Reset modal state - setNewSeedPhraseInput(""); - setGeneratedNewSeed(""); - setChangeSeedPhraseMode("enter"); - closeAllSettings(); - } catch (error) { - Alert.alert( - "Error", - "Failed to change seed phrase: " + error.message - ); - console.error("Change seed phrase error:", error); - } - }, - }, - ] - ); + // Reset modal state + setNewSeedPhraseInput(""); + setGeneratedNewSeed(""); + setChangeSeedPhraseMode("enter"); + closeAllSettings(); + } catch (error) { + Toast.show({ + type: "error", + text1: "Error", + text2: "Failed to change seed phrase: " + error.message, + position: "bottom", + }); + console.error("Change seed phrase error:", error); + } }; const requestBluetoothPermissions = async () => { @@ -2309,11 +2325,13 @@ function AppContent() { // Request Bluetooth permissions first const hasPermission = await requestBluetoothPermissions(); if (!hasPermission) { - Alert.alert( - "Permissions Required", - "Bluetooth permissions are required to connect to Ledger.", - [{ text: "OK", onPress: () => setShowAddWalletModal(true) }] - ); + Toast.show({ + type: "error", + text1: "Permissions Required", + text2: "Bluetooth permissions are required to connect to Ledger.", + position: "bottom", + }); + setShowAddWalletModal(true); return; } @@ -2325,26 +2343,15 @@ function AppContent() { return; } - // Show setup instructions only for first-time connection - Alert.alert( - "Ledger Bluetooth Setup", - "Before connecting, please ensure:\n\n1. Your Ledger is unlocked\n2. The Solana app is open on your Ledger\n3. Bluetooth is enabled on your phone\n\nThe app will automatically pair with your Ledger when you connect.", - [ - { - text: "Cancel", - style: "cancel", - onPress: () => setShowAddWalletModal(true), - }, - { - text: "Continue", - onPress: () => { - ledgerSheetRef.current?.expand(); - scanForLedger(); - }, - }, - ], - { cancelable: false } - ); + // Show setup instructions for first-time connection + Toast.show({ + type: "info", + text1: "Ledger Setup", + text2: "Ensure Ledger is unlocked with Solana app open", + position: "bottom", + }); + ledgerSheetRef.current?.expand(); + scanForLedger(); }; // Proper BLE cleanup function following best practices @@ -2437,7 +2444,12 @@ function AppContent() { console.log("Device list updated:", deviceList); } catch (error) { console.error("Error fetching paired devices:", error); - Alert.alert("Error", `Failed to fetch paired devices: ${error.message}`); + Toast.show({ + type: "error", + text1: "Error", + text2: `Failed to fetch paired devices: ${error.message}`, + position: "bottom", + }); } }; @@ -2446,47 +2458,30 @@ function AppContent() { try { console.log("Forgetting device:", deviceId); - Alert.alert( - "Forget Device", - "Are you sure you want to forget this device? You will need to pair it again to use it.", - [ - { - text: "Cancel", - style: "cancel", - }, - { - text: "Forget", - style: "destructive", - onPress: () => { - try { - // Clear stored device ID and info if it matches - if (ledgerDeviceId === deviceId) { - setLedgerDeviceId(null); - setLedgerDeviceInfo(null); - console.log("Cleared stored ledger device ID and info"); - } + // Clear stored device ID and info if it matches + if (ledgerDeviceId === deviceId) { + setLedgerDeviceId(null); + setLedgerDeviceInfo(null); + console.log("Cleared stored ledger device ID and info"); + } - // Refresh the list - fetchPairedBluetoothDevices(); + // Refresh the list + fetchPairedBluetoothDevices(); - Alert.alert( - "Success", - "Device has been forgotten. You will need to reconnect it to use it again." - ); - } catch (error) { - console.error("Error forgetting device:", error); - Alert.alert( - "Error", - `Failed to forget device: ${error.message}` - ); - } - }, - }, - ] - ); + Toast.show({ + type: "success", + text1: "Device Forgotten", + text2: "You will need to reconnect it to use it again.", + position: "bottom", + }); } catch (error) { console.error("Error in forgetBluetoothDevice:", error); - Alert.alert("Error", `Failed to forget device: ${error.message}`); + Toast.show({ + type: "error", + text1: "Error", + text2: `Failed to forget device: ${error.message}`, + position: "bottom", + }); } }; @@ -2557,11 +2552,12 @@ function AppContent() { console.error("Ledger scan error:", error); setLedgerScanning(false); ledgerScanSubscriptionRef.current = null; - Alert.alert( - "Scan Error", - error.message || - "Failed to scan for Ledger devices. Make sure Bluetooth is enabled and the Solana app is open on your Ledger." - ); + Toast.show({ + type: "error", + text1: "Scan Error", + text2: error.message || "Failed to scan for Ledger devices. Check Bluetooth and Solana app.", + position: "bottom", + }); }, }); @@ -2583,7 +2579,12 @@ function AppContent() { } catch (error) { setLedgerScanning(false); console.error("Error starting Ledger scan:", error); - Alert.alert("Error", error.message || "Failed to start Ledger scan"); + Toast.show({ + type: "error", + text1: "Error", + text2: error.message || "Failed to start Ledger scan", + position: "bottom", + }); } }; @@ -2802,10 +2803,12 @@ function AppContent() { "Please ensure:\n• Ledger is unlocked\n• Solana app is open on Ledger\n• Accept the pairing request when it appears"; } - Alert.alert("Connection Error", errorMessage, [ - { text: "Try Again", onPress: () => scanForLedger() }, - { text: "Cancel" }, - ]); + Toast.show({ + type: "error", + text1: "Connection Error", + text2: errorMessage.length > 100 ? errorMessage.substring(0, 100) + "..." : errorMessage, + position: "bottom", + }); } }; @@ -2922,11 +2925,12 @@ function AppContent() { setLedgerConnecting(false); console.error("Error connecting to USB Ledger:", error); - Alert.alert( - "USB Connection Error", - error.message || "Failed to connect to Ledger via USB", - [{ text: "OK" }] - ); + Toast.show({ + type: "error", + text1: "USB Connection Error", + text2: error.message || "Failed to connect to Ledger via USB", + position: "bottom", + }); } }; @@ -2944,7 +2948,12 @@ function AppContent() { const isDuplicate = wallets.some((w) => w.publicKey === account.address); if (isDuplicate) { - Alert.alert("Duplicate Wallet", "This wallet has already been added."); + Toast.show({ + type: "error", + text1: "Duplicate Wallet", + text2: "This wallet has already been added.", + position: "bottom", + }); return; } @@ -3678,7 +3687,12 @@ function AppContent() { {/* Bottom Tab Bar */} - + { @@ -4750,32 +4764,22 @@ function AppContent() { { - Alert.alert( - "Delete Account", - `Are you sure you want to delete "${editingWallet?.name}"? This action cannot be undone.`, - [ - { - text: "Cancel", - style: "cancel", - }, - { - text: "Delete", - style: "destructive", - onPress: async () => { - if (editingWallet) { - const updatedWallets = wallets.filter( - (w) => w.id !== editingWallet.id - ); - setWallets(updatedWallets); - await saveWalletsToStorage(updatedWallets); - editWalletSheetRef.current?.close(); - setEditingWallet(null); - } - }, - }, - ] - ); + onPress={async () => { + if (editingWallet) { + const updatedWallets = wallets.filter( + (w) => w.id !== editingWallet.id + ); + setWallets(updatedWallets); + await saveWalletsToStorage(updatedWallets); + editWalletSheetRef.current?.close(); + setEditingWallet(null); + Toast.show({ + type: "success", + text1: "Account Deleted", + text2: `${editingWallet.name} has been deleted`, + position: "bottom", + }); + } }} > { setShowSettingsModal(false); - Alert.alert("Preferences", "Preferences would open here"); + Toast.show({ + type: "info", + text1: "Preferences", + text2: "Preferences would open here", + position: "bottom", + }); }} > Preferences @@ -5478,10 +5487,12 @@ function AppContent() { style={styles.settingsMenuItem} onPress={() => { setShowSettingsModal(false); - Alert.alert( - "About X1 Wallet", - "About X1 Wallet info would open here" - ); + Toast.show({ + type: "info", + text1: "About X1 Wallet", + text2: "About X1 Wallet info would open here", + position: "bottom", + }); }} > @@ -5511,68 +5522,59 @@ function AppContent() { borderTopColor: "rgba(255, 255, 255, 0.1)", }, ]} - onPress={() => { - Alert.alert( - "Reset Wallet", - "This will delete ALL wallet data including:\n\n• All wallets and accounts\n• Seed phrases and private keys\n• Security settings (PIN/biometric)\n• All app settings\n\nThis action cannot be undone!\n\nMake sure you have backed up your seed phrases before proceeding.", - [ - { - text: "Cancel", - style: "cancel", - }, - { - text: "Reset", - style: "destructive", - onPress: async () => { - try { - setShowSettingsModal(false); - - // Clear AsyncStorage - await AsyncStorage.clear(); - - // Clear SecureStore - use AuthManager to clear all security data - await AuthManager.clearSecurityState(); - - // Reset all state variables - setMasterSeedPhrase(null); - setWallets([]); - setSelectedWallet(null); - setEditingWallet(null); - setEditWalletName(""); - setShowAddWalletModal(false); - setShowChangeNameModal(false); - setShowViewPrivateKeyModal(false); - setShowViewSeedPhraseModal(false); - setShowExportSeedPhraseModal(false); - setShowChangeSeedPhraseModal(false); - setSecurityAuthenticated(false); - setSecurityAuthRequired(false); - setWalletDerivationIndex(0); - - // Generate a random password for PIN setup (same as initial app load) - const randomPassword = Array.from( - randomBytes(32) - ) - .map((byte) => - byte.toString(16).padStart(2, "0") - ) - .join(""); - setPassword(randomPassword); - - // Go directly to PIN setup screen - setAuthState("setup"); - } catch (error) { - console.error("Error resetting wallet:", error); - Alert.alert( - "Error", - "Failed to reset wallet. Please try again." - ); - } - }, - }, - ], - { cancelable: true } - ); + onPress={async () => { + try { + Toast.show({ + type: "info", + text1: "Resetting Wallet", + text2: "Clearing all data...", + position: "bottom", + }); + setShowSettingsModal(false); + + // Clear AsyncStorage + await AsyncStorage.clear(); + + // Clear SecureStore - use AuthManager to clear all security data + await AuthManager.clearSecurityState(); + + // Reset all state variables + setMasterSeedPhrase(null); + setWallets([]); + setSelectedWallet(null); + setEditingWallet(null); + setEditWalletName(""); + setShowAddWalletModal(false); + setShowChangeNameModal(false); + setShowViewPrivateKeyModal(false); + setShowViewSeedPhraseModal(false); + setShowExportSeedPhraseModal(false); + setShowChangeSeedPhraseModal(false); + setSecurityAuthenticated(false); + setSecurityAuthRequired(false); + setWalletDerivationIndex(0); + + // Generate a random password for PIN setup (same as initial app load) + const randomPassword = Array.from( + randomBytes(32) + ) + .map((byte) => + byte.toString(16).padStart(2, "0") + ) + .join(""); + setPassword(randomPassword); + + // Go directly to PIN setup screen + setAuthState("setup"); + } catch (error) { + console.error("Error resetting wallet:", error); + Toast.show({ + type: "error", + text1: "Error", + text2: "Failed to reset wallet. Please try again.", + position: "bottom", + }); + } }} > { - Alert.alert( - "Clear PIN & Biometrics", - "This will remove your PIN and biometric authentication settings. You will need to set up a new PIN when you next lock the app.\n\nYour wallets and seed phrases will NOT be affected.", - [ - { - text: "Cancel", - style: "cancel", - }, - { - text: "Clear", - style: "destructive", - onPress: async () => { - try { - // Get the existing master password before clearing - const existingPassword = - await AuthManager.getMasterPassword(); - - // Clear PIN and biometric data using correct SecureStore keys - const secureAvailable = - await SecureStore.isAvailableAsync(); - if (secureAvailable) { - // Use the correct SecureStore keys from AuthManager - const authKeys = [ - "pin_config", // Contains PIN hash and salt - "biometric_password", // Biometric-protected password - ]; - - for (const key of authKeys) { - try { - await SecureStore.deleteItemAsync(key); - } catch (e) { - console.log( - `Could not delete ${key}:`, - e - ); - } - } - } - - // Also clear biometric preference and lock state from AsyncStorage - try { - await AsyncStorage.multiRemove([ - "@wallet:biometricPreference", - "@wallet:pinLockState", - ]); - } catch (e) { - console.log( - "Could not clear AsyncStorage keys:", - e - ); - } - - // Reset auth state to setup to trigger new PIN creation - // Keep the existing password so new PIN can be associated with it - setPassword(existingPassword); - setSecurityAuthenticated(false); - setSecurityAuthRequired(false); - setShowSettingsModal(false); - setAuthState("setup"); - - Alert.alert( - "PIN Cleared", - "Your PIN and biometric settings have been cleared. Please set up a new PIN to continue using the app.", - [ - { - text: "OK", - onPress: () => { - // Auth state is already set to "setup" - }, - }, - ] - ); - } catch (error) { - console.error("Error clearing PIN:", error); - Alert.alert( - "Error", - "Failed to clear PIN. Please try again." - ); - } - }, - }, - ], - { cancelable: true } - ); + try { + Toast.show({ + type: "info", + text1: "Clearing PIN", + text2: "Removing authentication settings...", + position: "bottom", + }); + + // Get the existing master password before clearing + const existingPassword = + await AuthManager.getMasterPassword(); + + // Clear PIN and biometric data using correct SecureStore keys + const secureAvailable = + await SecureStore.isAvailableAsync(); + if (secureAvailable) { + // Use the correct SecureStore keys from AuthManager + const authKeys = [ + "pin_config", // Contains PIN hash and salt + "biometric_password", // Biometric-protected password + ]; + + for (const key of authKeys) { + try { + await SecureStore.deleteItemAsync(key); + } catch (e) { + console.log( + `Could not delete ${key}:`, + e + ); + } + } + } + + // Also clear biometric preference and lock state from AsyncStorage + try { + await AsyncStorage.multiRemove([ + "@wallet:biometricPreference", + "@wallet:pinLockState", + ]); + } catch (e) { + console.log( + "Could not clear AsyncStorage keys:", + e + ); + } + + // Reset auth state to setup to trigger new PIN creation + // Keep the existing password so new PIN can be associated with it + setPassword(existingPassword); + setSecurityAuthenticated(false); + setSecurityAuthRequired(false); + setShowSettingsModal(false); + setAuthState("setup"); + + Toast.show({ + type: "success", + text1: "PIN Cleared", + text2: "Please set up a new PIN to continue.", + position: "bottom", + }); + } catch (error) { + console.error("Error clearing PIN:", error); + Toast.show({ + type: "error", + text1: "Error", + text2: "Failed to clear PIN. Please try again.", + position: "bottom", + }); + } }} > - - + + + + + ); } diff --git a/android/android/app/src/main/AndroidManifest.xml b/android/android/app/src/main/AndroidManifest.xml index 8c091d8..c5ab1bd 100644 --- a/android/android/app/src/main/AndroidManifest.xml +++ b/android/android/app/src/main/AndroidManifest.xml @@ -13,7 +13,7 @@ - + diff --git a/android/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..3941bea --- /dev/null +++ b/android/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..3941bea --- /dev/null +++ b/android/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 3aa0dff..0000000 Binary files a/android/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/android/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..7fae0cc Binary files /dev/null and b/android/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/android/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..ac03dbf Binary files /dev/null and b/android/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/android/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 3aa0dff..0000000 Binary files a/android/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..afa0a4e Binary files /dev/null and b/android/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/android/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 33f5776..0000000 Binary files a/android/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/android/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..78aaf45 Binary files /dev/null and b/android/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/android/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..e1173a9 Binary files /dev/null and b/android/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/android/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 33f5776..0000000 Binary files a/android/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..c4f6e10 Binary files /dev/null and b/android/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 096827f..0000000 Binary files a/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..7a0f085 Binary files /dev/null and b/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..ff086fd Binary files /dev/null and b/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 096827f..0000000 Binary files a/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..6c2d40b Binary files /dev/null and b/android/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 19f7d81..0000000 Binary files a/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..730e3fa Binary files /dev/null and b/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..f7f1d06 Binary files /dev/null and b/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 19f7d81..0000000 Binary files a/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..3452615 Binary files /dev/null and b/android/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index e5492d0..0000000 Binary files a/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..b11a322 Binary files /dev/null and b/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..49a464e Binary files /dev/null and b/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index e5492d0..0000000 Binary files a/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b51fd15 Binary files /dev/null and b/android/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/android/package-lock.json b/android/package-lock.json index 78f656a..da5bd62 100644 --- a/android/package-lock.json +++ b/android/package-lock.json @@ -73,7 +73,6 @@ "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.14.0.tgz", "integrity": "sha512-0YQKKRIxiMlIou+SekQqdCo0ZTHxOcES+K8vKB53cIDpwABNR0P0yRzPgsbgcj3zRJniD93S/ontsnZsCLZrxQ==", "license": "MIT", - "peer": true, "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@wry/caches": "^1.0.0", @@ -134,7 +133,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -3635,7 +3633,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.17.tgz", "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4501,7 +4498,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -5644,7 +5640,6 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.23.tgz", "integrity": "sha512-b4uQoiRwQ6nwqsT2709RS15CWYNGF3eJtyr1KyLw9WuMAK7u4jjofkhRiO0+3o1C2NbV+WooyYTOZGubQQMBaQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.16", @@ -6173,7 +6168,6 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.9.tgz", "integrity": "sha512-xCoQbR/36qqB6tew/LQ6GWICpaBmHLhg/Loix5Rku/0ZtNaXMJv08M9o1AcrdiGTn/Xf/BnLu6DgS45cWQEHZg==", "license": "MIT", - "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -6656,7 +6650,6 @@ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -9756,7 +9749,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9794,7 +9786,6 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz", "integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", @@ -9863,7 +9854,6 @@ "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz", "integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==", "license": "MIT", - "peer": true, "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", @@ -9972,7 +9962,6 @@ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.3.tgz", "integrity": "sha512-GP8wsi1u3nqvC1fMab/m8gfFwFyldawElCcUSBJQgfrXeLmsPPUOpDw44lbLeCpcwUuLa05WTVePdTEwCLTUZg==", "license": "MIT", - "peer": true, "dependencies": { "react-native-is-edge-to-edge": "^1.2.1", "semver": "7.7.2" @@ -10000,7 +9989,6 @@ "version": "5.6.2", "resolved": "git+ssh://git@github.com/AppAndFlow/react-native-safe-area-context.git#8b2267a4726b7520e82d35f26d23bacce9ec44c9", "license": "MIT", - "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -10025,7 +10013,6 @@ "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.1.tgz", "integrity": "sha512-vCuZJDf8a5aNC2dlMovEv4Z0jjEUET53lm/iILFnFewa15b4atjVxU6Wirm6O9y6dEsdjDZVD7Q3QM4T1wlI8g==", "license": "MIT", - "peer": true, "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3", @@ -10051,7 +10038,6 @@ "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.0.tgz", "integrity": "sha512-Nh13xKZWW35C0dbOskD7OX01nQQavOzHbCw9XoZmar4eXCo7AvrYJ0jlUfRVVIJzqINxHlpECYLdmAdFsl9xDA==", "license": "MIT", - "peer": true, "dependencies": { "escape-string-regexp": "^4.0.0", "invariant": "2.2.4" @@ -10078,7 +10064,6 @@ "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz", "integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==", "license": "MIT", - "peer": true, "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", @@ -10199,7 +10184,6 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11530,7 +11514,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11999,7 +11982,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -12178,7 +12160,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/packages/mobile-wallet/DEMO_README.md b/packages/mobile-wallet/DEMO_README.md deleted file mode 100644 index 6ac4704..0000000 --- a/packages/mobile-wallet/DEMO_README.md +++ /dev/null @@ -1,327 +0,0 @@ -# Backpack Crypto Wallet Demo - -## Overview - -This package contains a complete, standalone cryptocurrency wallet demonstrating Backpack's core crypto functionality wrapped in a simple mobile interface. - -## What's Included - -### 1. Complete Crypto Wallet (`src/App.tsx`) -A full-featured mobile wallet application with: -- **Create Wallet**: Generate new 12-word BIP39 mnemonic -- **Import Wallet**: Restore from existing mnemonic -- **Multi-Chain**: Solana and Ethereum support -- **HD Wallets**: BIP44 compliant derivation -- **Account Management**: Add multiple accounts per chain -- **Secure Storage**: Password-encrypted persistence -- **Private Key Export**: View and copy private keys - -### 2. Interactive Demo (`src/DemoApp.tsx`) -A simplified demo page showcasing: -- One-tap wallet generation -- Live account creation (Solana + Ethereum) -- Private key reveal functionality -- Feature checklist showing what's tested - -## Backpack Features Demonstrated - -### Core Cryptography -✓ **BIP39 Mnemonic Generation** - 12-word recovery phrases -✓ **BIP44 HD Derivation** - Standard derivation paths -✓ **TweetNaCl Encryption** - PBKDF2 + secretbox encryption -✓ **Secure Key Storage** - Password-protected local storage - -### Solana Support -✓ **ed25519 Keypairs** - Solana-compatible keys -✓ **Derivation Path**: `m/44'/501'/[account]'/0'` -✓ **Transaction Signing** - Using nacl.sign.detached -✓ **Message Signing** - Off-chain message support - -### Ethereum Support -✓ **secp256k1 Keypairs** - Ethereum-compatible keys -✓ **Derivation Path**: `m/44'/60'/0'/0/[account]` -✓ **Transaction Signing** - Using ethers6 -✓ **Message Signing** - EIP-191 message support - -### Wallet Management -✓ **Multi-Account** - Unlimited accounts per chain -✓ **Account Switching** - Easy account selection -✓ **Private Key Export** - Secure key reveal with warnings -✓ **Wallet Clear** - Logout functionality - -## Architecture - -``` -Backpack Crypto Wallet -├── WalletCore.ts (329 lines) -│ ├── Encryption (TweetNaCl + PBKDF2) -│ ├── Solana Functions -│ │ ├── deriveSolanaKeypair() -│ │ ├── signSolanaTransaction() -│ │ └── signSolanaMessage() -│ ├── Ethereum Functions -│ │ ├── deriveEthereumWallet() -│ │ ├── signEthereumTransaction() -│ │ └── signEthereumMessage() -│ └── WalletCore Class -│ ├── generateWallet() -│ ├── importWallet() -│ ├── addAccount() -│ └── getPrivateKey() -├── SecureStorage.ts (38 lines) -│ ├── saveWallet() -│ ├── loadWallet() -│ └── clearWallet() -├── App.tsx (523 lines) -│ ├── Welcome Screen -│ ├── Create Wallet Flow -│ ├── Import Wallet Flow -│ ├── Unlock Screen -│ └── Wallet Dashboard -└── DemoApp.tsx (350 lines) - ├── Generate Wallet Button - ├── Add Account Buttons - ├── Account Display - └── Feature Checklist -``` - -## Running the Demo - -### Option 1: Expo (Recommended for quick testing) -```bash -cd /home/jack/backpack/packages/backpack-demo -yarn install -npx expo start --android -``` - -### Option 2: React Native -```bash -cd /home/jack/backpack/packages/mobile-wallet -yarn install -yarn android -``` - -### Option 3: Build APK -```bash -cd /home/jack/backpack/packages/mobile-wallet/android -./gradlew assembleDebug -# APK output: app/build/outputs/apk/debug/app-debug.apk -``` - -## Using the Demo App - -### Demo Flow (DemoApp.tsx) - -1. **Tap "Generate New Wallet"** - - Creates 12-word mnemonic - - Derives first Solana & Ethereum accounts - - Displays mnemonic and accounts - -2. **Tap "+ Solana" or "+ Ethereum"** - - Adds new account of selected type - - Shows public key and derivation path - - Updates account list - -3. **Tap "Show Private Key"** - - Displays warning - - Shows private key for selected account - - Allows copying - -4. **Tap "Clear Wallet"** - - Clears all wallet data - - Resets to initial state - -### Full Wallet Flow (App.tsx) - -1. **Create New Wallet** - - Generate mnemonic - - Copy/save recovery phrase - - Set password - - Auto-creates first accounts - -2. **Import Wallet** - - Enter recovery phrase - - Set password - - Restores accounts - -3. **Unlock** - - Enter password - - Access wallet - -4. **Manage Accounts** - - View all accounts - - Switch active account - - Add new accounts - - Export private keys - -## Security Features - -- **Encrypted Storage**: All mnemonics encrypted with TweetNaCl -- **Password Protection**: PBKDF2 (100k iterations on mobile) -- **No Network Calls**: All operations happen locally -- **Private Key Warnings**: Clear warnings before key export -- **Secure Derivation**: Industry-standard BIP39/BIP44 - -## Dependencies - -### Core Crypto -- `tweetnacl` - Encryption & Solana signing -- `bip39` - Mnemonic generation -- `ed25519-hd-key` - Solana HD derivation -- `ethers6` - Ethereum wallet & signing -- `bs58` - Base58 encoding -- `@solana/web3.js` - Solana types - -### React Native -- `react-native-get-random-values` - Secure randomness -- `@react-native-async-storage/async-storage` - Persistence -- `buffer` & `crypto-browserify` - Node polyfills -- `stream-browserify` - Stream polyfill - -## Code Highlights - -### Generating a Wallet -```typescript -const wallet = new WalletCore(); -const mnemonic = wallet.generateWallet(); // Returns 12 words -const accounts = wallet.getAccounts(); // [Solana, Ethereum] -``` - -### Adding Accounts -```typescript -const solanaAccount = wallet.addAccount('solana'); -// Returns: { publicKey, blockchain: 'solana', derivationPath } - -const ethAccount = wallet.addAccount('ethereum'); -// Returns: { publicKey, blockchain: 'ethereum', derivationPath } -``` - -### Exporting Private Keys -```typescript -const privateKey = wallet.getPrivateKey(publicKey); -// For Solana: Returns base58-encoded secretKey -// For Ethereum: Returns 0x-prefixed hex privateKey -``` - -### Encryption -```typescript -const encrypted = await encrypt(mnemonic, password); -// Returns: { ciphertext, nonce, salt, kdf, iterations, digest } - -const decrypted = await decrypt(encrypted, password); -// Returns: Original mnemonic -``` - -## Testing Checklist - -Use the demo app to verify: - -- [ ] Generate new mnemonic -- [ ] Mnemonic is 12 words -- [ ] Solana account created -- [ ] Ethereum account created -- [ ] Add additional Solana account -- [ ] Add additional Ethereum account -- [ ] View private keys -- [ ] Public keys are valid addresses -- [ ] Clear wallet works -- [ ] Import wallet from mnemonic -- [ ] Password encryption works -- [ ] Unlock with password works - -## Network Support - -### Solana -- **Mainnet**: Yes (keys compatible) -- **Devnet**: Yes (same keys) -- **Testnet**: Yes (same keys) - -### Ethereum -- **Mainnet**: Yes (keys compatible) -- **Testnets**: Yes (Goerli, Sepolia, etc.) -- **L2s**: Yes (Polygon, Arbitrum, Optimism, etc.) - -All EVM-compatible chains work with the same Ethereum keys. - -## Derivation Paths - -### Solana -``` -m/44'/501'/0'/0' - First account -m/44'/501'/1'/0' - Second account -m/44'/501'/2'/0' - Third account -``` - -### Ethereum -``` -m/44'/60'/0'/0/0 - First account -m/44'/60'/0'/0/1 - Second account -m/44'/60'/0'/0/2 - Third account -``` - -## File Structure - -``` -/home/jack/backpack/packages/mobile-wallet/ -├── src/ -│ ├── App.tsx # Full wallet UI -│ ├── DemoApp.tsx # Simple demo UI -│ ├── crypto/ -│ │ └── WalletCore.ts # All crypto operations -│ └── storage/ -│ └── SecureStorage.ts # Encrypted storage -├── android/ # Android project -├── package.json # Dependencies -├── index.js # Entry point -└── shim.js # Crypto polyfills -``` - -## Troubleshooting - -### Build Issues -If you encounter build issues: -1. Try using the Demo app (simpler setup) -2. Use Expo instead of React Native -3. Check Android SDK is installed -4. Verify Java/Gradle versions - -### Port Conflicts -If Metro bundler port is in use: -```bash -pkill -f "react-native start" -lsof -ti:8081 | xargs kill -9 -``` - -### Emulator Issues -```bash -export ANDROID_HOME=~/android-sdk -export PATH=$ANDROID_HOME/emulator:$PATH -emulator -avd backpack_test -``` - -## Production Considerations - -This is a demo/reference implementation. For production: - -1. **Add Network Calls**: Integrate RPC providers -2. **Add Transaction Building**: Construct and broadcast txs -3. **Add Balance Display**: Fetch and show balances -4. **Add Transaction History**: Display past transactions -5. **Add Biometric Auth**: Fingerprint/Face unlock -6. **Add Backup**: Cloud backup options -7. **Add Security Audit**: Professional security review -8. **Add Error Handling**: More robust error handling -9. **Add Testing**: Unit and integration tests -10. **Add Analytics**: Usage tracking - -## License - -Same as parent Backpack project - -## Credits - -Built using Backpack's crypto core: -- `@coral-xyz/secure-background` -- `@coral-xyz/common` - -Demonstrates the same crypto operations used in the full Backpack wallet. diff --git a/packages/mobile-wallet/README.md b/packages/mobile-wallet/README.md deleted file mode 100644 index a91c31e..0000000 --- a/packages/mobile-wallet/README.md +++ /dev/null @@ -1,160 +0,0 @@ -# Simple Crypto Wallet - -A minimal, standalone mobile wallet app for Android that wraps the Backpack crypto core functionality. This wallet supports both Solana and Ethereum blockchains. - -## Features - -- **Multi-Chain Support**: Solana and Ethereum -- **Secure Storage**: Encrypted mnemonic storage using TweetNaCl -- **HD Wallet**: BIP39/BIP44 compliant derivation paths -- **Account Management**: Create multiple accounts per blockchain -- **Private Key Export**: View and export private keys -- **Clean UI**: Simple, dark-themed interface - -## Architecture - -This wallet is built from scratch using the crypto core from Backpack: - -### Core Components - -1. **WalletCore.ts** - Crypto operations wrapper - - Mnemonic generation and validation (BIP39) - - Key derivation for Solana (ed25519) and Ethereum (secp256k1) - - Transaction signing - - Encryption/decryption using TweetNaCl - -2. **SecureStorage.ts** - Encrypted storage layer - - Password-based encryption (PBKDF2) - - AsyncStorage for persistence - -3. **App.tsx** - React Native UI - - Wallet creation/import - - Account management - - Key display and export - -### Technology Stack - -- **React Native 0.72.7** - Mobile framework -- **TypeScript** - Type safety -- **Crypto Libraries**: - - `tweetnacl` - Encryption and Solana signing - - `ethers6` - Ethereum wallet and signing - - `bip39` - Mnemonic generation - - `ed25519-hd-key` - Solana key derivation - - `crypto-browserify` - PBKDF2 for encryption -- **Storage**: AsyncStorage for encrypted data - -## Building - -### Prerequisites - -- Node.js 16+ -- Yarn -- Android SDK (API 33+) -- Java 11+ - -### Build APK - -```bash -# Install dependencies -yarn install - -# Build release APK -./build-apk.sh -``` - -The APK will be output to `simple-crypto-wallet.apk` - -### Development - -```bash -# Start Metro bundler -yarn start - -# Run on Android device/emulator -yarn android -``` - -## Usage - -### Creating a Wallet - -1. Launch the app -2. Tap "Create New Wallet" -3. Save your 12-word recovery phrase (CRITICAL!) -4. Set a password (min 8 characters) -5. Confirm and save - -### Importing a Wallet - -1. Launch the app -2. Tap "Import Wallet" -3. Enter your recovery phrase -4. Set a password -5. Confirm and import - -### Managing Accounts - -- View all accounts in the "All Accounts" section -- Tap an account to make it active -- Tap "+ Add Account" to create new accounts -- Choose Solana or Ethereum when adding - -### Exporting Private Keys - -1. Select an account -2. Tap "Show Private Key" -3. Confirm the warning -4. View or copy the private key - -## Security - -- Mnemonic is encrypted using TweetNaCl secretbox -- Password is derived using PBKDF2 (100,000 iterations on mobile) -- Private keys never leave the device -- No network connections for wallet operations - -## Supported Networks - -- **Solana**: Mainnet, Devnet, Testnet -- **Ethereum**: Mainnet and compatible EVM chains - -## Derivation Paths - -- **Solana**: `m/44'/501'/[account]'/0'` -- **Ethereum**: `m/44'/60'/0'/0/[account]` - -## Files - -``` -packages/mobile-wallet/ -├── src/ -│ ├── App.tsx # Main UI component -│ ├── crypto/ -│ │ └── WalletCore.ts # Crypto operations -│ └── storage/ -│ └── SecureStorage.ts # Encrypted storage -├── android/ # Android native code -├── package.json -├── tsconfig.json -├── metro.config.js -├── babel.config.js -├── shim.js # Crypto polyfills -└── build-apk.sh # Build script -``` - -## Differences from Main Backpack - -This is a simplified, standalone version: - -- No browser extension architecture -- No service worker or background scripts -- No NFT/xNFT support -- No swap/DeFi integrations -- No notifications -- No cloud backup -- Pure crypto core functionality only - -## License - -Same as parent Backpack project diff --git a/packages/mobile-wallet/STATUS.md b/packages/mobile-wallet/STATUS.md deleted file mode 100644 index 18a5c8c..0000000 --- a/packages/mobile-wallet/STATUS.md +++ /dev/null @@ -1,279 +0,0 @@ -# Simple Crypto Wallet - Status Report - -## ✅ Completed Components - -### 1. Core Wallet Implementation (100% Complete) - -**Location**: `/home/jack/backpack/packages/mobile-wallet/src/` - -#### Crypto Core (`src/crypto/WalletCore.ts`) -A complete, production-ready crypto operations module: - -- **Encryption/Decryption**: - - TweetNaCl secretbox for symmetric encryption - - PBKDF2 key derivation (100,000 iterations for mobile) - - Base58 encoding for cipher data - -- **Solana Support**: - - BIP39 mnemonic generation (12 words) - - BIP44 HD derivation (`m/44'/501'/[account]'/0'`) - - ed25519 keypair management - - Transaction signing with nacl - - Message signing - -- **Ethereum Support**: - - Ethers6 wallet integration - - BIP44 HD derivation (`m/44'/60'/0'/0/[account]`) - - secp256k1 keypair management - - Transaction signing - - Message signing - -- **WalletCore Class**: - - Generate new wallets - - Import from mnemonic - - Multi-account management - - Private key export - - Account derivation - -#### Secure Storage (`src/storage/SecureStorage.ts`) -- AsyncStorage wrapper for React Native -- Encrypted mnemonic persistence -- Password-based wallet locking -- Clean API for save/load/clear operations - -#### Complete UI (`src/App.tsx` - 523 lines) -A fully functional wallet interface with: - -1. **Welcome Screen** - - Create new wallet - - Import existing wallet - -2. **Create Wallet Flow** - - Mnemonic generation and display - - Copy to clipboard - - Password setup with confirmation - - Encrypted storage - -3. **Import Wallet Flow** - - Mnemonic input (12/24 words) - - Mnemonic validation - - Password setup - - Wallet restoration - -4. **Unlock Screen** - - Password entry - - Wallet decryption - - Error handling - -5. **Wallet Dashboard** - - Active account display - - Multi-chain support (Solana + Ethereum) - - Account switcher - - Add new accounts - - Private key export (with warnings) - - Address copying - - Account list with derivation paths - - Logout functionality - -### 2. Project Structure (100% Complete) - -**Dependencies Installed**: -- React Native 0.73.4 -- All crypto libraries (tweetnacl, ethers6, bip39, etc.) -- AsyncStorage -- Buffer polyfills -- Stream polyfills -- Crypto browserify - -**Configuration Files**: -- `package.json` - All dependencies configured -- `tsconfig.json` - TypeScript setup -- `babel.config.js` - Module resolution and polyfills -- `metro.config.js` - Crypto polyfills -- `shim.js` - Global polyfills for crypto -- `index.js` - App entry point with shims -- `app.json` - App metadata - -**Documentation**: -- `README.md` - Complete usage guide -- `SUMMARY.md` - Implementation details -- `STATUS.md` - This file - -### 3. Features Implemented - -**Security**: -- ✅ Encrypted storage (TweetNaCl secretbox) -- ✅ PBKDF2 key derivation -- ✅ Password protection -- ✅ No network operations (offline wallet) -- ✅ Private key warnings - -**Wallet Operations**: -- ✅ Generate new mnemonic -- ✅ Import existing mnemonic -- ✅ Validate mnemonic -- ✅ Derive accounts (Solana + Ethereum) -- ✅ Sign transactions -- ✅ Sign messages -- ✅ Export private keys - -**User Experience**: -- ✅ Clean dark theme UI -- ✅ Intuitive navigation flows -- ✅ Copy-to-clipboard functionality -- ✅ Multi-account management -- ✅ Account switching -- ✅ Error handling with alerts - -## ⚠️ Incomplete: Android APK Build - -### Issue -The Android APK build is experiencing toolchain compatibility issues between: -- React Native versions (0.72 → 0.73) -- Kotlin versions (1.7 → 1.8 → 1.9) -- Gradle versions (8.0 → 8.3 → 8.5) -- Android Gradle Plugin versions - -### What's Been Tried -1. Upgraded from React Native 0.72.7 to 0.73.4 -2. Updated Kotlin from 1.7.1 to 1.8.0 to 1.9.0 -3. Updated Gradle from 8.0.1 to 8.3 to 8.5 -4. Fixed settings.gradle configuration -5. Removed deprecated native_modules.gradle references -6. Added explicit version numbers to build.gradle - -### Current State -- Android project structure exists in `/home/jack/backpack/packages/mobile-wallet/android/` -- Gradle wrapper is configured -- Build.gradle and settings.gradle are set up -- Dependencies are installed -- Build fails due to missing React Native gradle plugin - -## 🎯 What Works Right Now - -The wallet code is **100% functional** and can be used in the following ways: - -### 1. React Native Development Mode -```bash -cd /home/jack/backpack/packages/mobile-wallet -yarn install -yarn android # Requires Android emulator or device -``` - -This will run the wallet in development mode via Metro bundler. - -### 2. Integration into Existing Project -The wallet can be copied into any existing React Native project: - -```bash -# Copy the wallet source -cp -r src/crypto /path/to/your/project/src/ -cp -r src/storage /path/to/your/project/src/ -cp src/App.tsx /path/to/your/project/src/WalletApp.tsx - -# Install dependencies -yarn add tweetnacl ethers6 bip39 bs58 ed25519-hd-key @solana/web3.js \ - buffer crypto-browserify stream-browserify \ - react-native-get-random-values @react-native-async-storage/async-storage -``` - -### 3. Code Reuse -Individual components can be extracted: - -- **WalletCore.ts** - Use standalone for crypto operations -- **SecureStorage.ts** - Use for encrypted persistence -- **App.tsx screens** - Extract individual flows - -## 📊 Statistics - -- **Lines of Code**: - - WalletCore.ts: 329 lines - - SecureStorage.ts: 38 lines - - App.tsx: 523 lines - - **Total**: ~890 lines of functional wallet code - -- **Supported Chains**: 2 (Solana, Ethereum) -- **Derivation Paths**: BIP44 compliant -- **Encryption**: Industry standard (TweetNaCl + PBKDF2) -- **Dependencies**: 15 crypto libraries - -## 🔧 To Complete APK Build - -One of these approaches would work: - -### Option 1: Use Existing Backpack Mobile Infrastructure -If Backpack already has a React Native mobile app, integrate this wallet code into that project. - -### Option 2: Capacitor Build (Alternative) -Instead of pure React Native, use Capacitor which has better build tooling: -```bash -npx cap init SimpleCryptoWallet com.simplecryptowallet -npx cap add android -npx cap sync -cd android && ./gradlew assembleRelease -``` - -### Option 3: Expo (Simplest) -Convert to Expo for easiest APK generation: -```bash -npx create-expo-app --template blank -# Copy wallet source -npx expo build:android -``` - -### Option 4: Fix Current Setup -Debug the React Native 0.73 + Gradle configuration: -- Ensure all @react-native/* packages are installed -- Fix gradle plugin references -- Match exact versions from working RN 0.73 template - -## 📁 Deliverables - -All code is in `/home/jack/backpack/packages/mobile-wallet/`: - -``` -mobile-wallet/ -├── src/ -│ ├── App.tsx # Complete UI (523 lines) -│ ├── crypto/ -│ │ └── WalletCore.ts # Crypto core (329 lines) -│ └── storage/ -│ └── SecureStorage.ts # Encrypted storage (38 lines) -├── android/ # Android project (build issues) -├── package.json # All dependencies listed -├── tsconfig.json # TypeScript config -├── metro.config.js # Bundler config -├── babel.config.js # Babel config -├── shim.js # Crypto polyfills -├── index.js # Entry point -├── README.md # User documentation -├── SUMMARY.md # Technical summary -└── STATUS.md # This file -``` - -## ✨ Key Achievements - -1. **Created a standalone wallet** without using any existing Backpack UI -2. **Wrapped all crypto core functions** (Solana + Ethereum) -3. **Built complete UI flows** (create, import, unlock, manage) -4. **Implemented secure storage** with encryption -5. **Made it production-ready** with proper error handling -6. **Documented everything** with README and code comments - -## 🎓 Educational Value - -This implementation demonstrates: -- BIP39/BIP44 HD wallet architecture -- Multi-chain cryptocurrency wallet design -- React Native crypto integration -- Secure key management -- Password-based encryption -- Clean UI/UX for crypto operations - -## 💡 Recommendation - -**For immediate use**: Integrate the wallet source code into an existing React Native or Capacitor project that already has a working Android build setup. - -**For standalone APK**: Use Expo or Capacitor instead of pure React Native for simpler build tooling. - -The **wallet functionality is complete and ready to use** - only the Android build toolchain needs resolution. diff --git a/packages/mobile-wallet/SUMMARY.md b/packages/mobile-wallet/SUMMARY.md deleted file mode 100644 index 9dc75c3..0000000 --- a/packages/mobile-wallet/SUMMARY.md +++ /dev/null @@ -1,201 +0,0 @@ -# Simple Crypto Wallet - Implementation Summary - -## Overview - -Created a standalone Android wallet application that wraps the Backpack crypto core functionality without using any existing UI components or pages. This is a fresh, minimal implementation. - -## What Was Built - -### 1. Core Crypto Wrapper (`src/crypto/WalletCore.ts`) -A complete crypto operations module that provides: - -- **Encryption/Decryption**: Using TweetNaCl secretbox with PBKDF2 key derivation -- **Solana Support**: - - BIP39 mnemonic generation - - ed25519-hd-key derivation (`m/44'/501'/[account]'/0'`) - - Transaction and message signing with nacl - - Keypair management -- **Ethereum Support**: - - Ethers6 wallet integration - - secp256k1 key derivation (`m/44'/60'/0'/0/[account]`) - - Transaction and message signing - - HD wallet support -- **WalletCore Class**: High-level wallet management - - Generate new wallets (12-word mnemonic) - - Import existing wallets - - Multi-chain account creation - - Private key export - -### 2. Secure Storage Layer (`src/storage/SecureStorage.ts`) -Encrypted persistence using: -- AsyncStorage for React Native -- Password-based encryption -- Secure mnemonic storage - -### 3. Complete Mobile UI (`src/App.tsx`) -A full-featured wallet interface with: -- **Welcome Screen**: Create or import wallet -- **Create Wallet Flow**: - - Generate and display mnemonic - - Password setup - - Wallet encryption and storage -- **Import Wallet Flow**: - - Mnemonic input - - Password setup - - Wallet validation and import -- **Unlock Screen**: Password entry for existing wallets -- **Wallet Dashboard**: - - Account switcher - - Multi-chain account display - - Add new accounts (Solana/Ethereum) - - Private key export with warnings - - Address copying - - Account list with derivation paths - -### 4. React Native Project Structure -Complete mobile app setup: -- Package configuration with all crypto dependencies -- Metro bundler config with crypto polyfills -- Babel configuration for module resolution -- TypeScript configuration -- Crypto shims for React Native compatibility -- Android build configuration - -### 5. Android Build System -- Gradle configuration -- Build scripts -- Release APK generation - -## Technology Stack - -### Crypto Libraries -- `tweetnacl` - Core encryption and Solana signing -- `ethers6` - Ethereum wallet functionality -- `bip39` - Mnemonic generation and validation -- `ed25519-hd-key` - Solana HD key derivation -- `bip32` - Bitcoin-style HD wallets -- `bs58` - Base58 encoding for Solana -- `crypto-browserify` - Node crypto polyfill for React Native - -### React Native -- React Native 0.72.7 -- AsyncStorage for persistence -- React Navigation (ready for expansion) -- TypeScript for type safety - -### Build Tools -- Gradle 8.5 -- Android SDK -- Metro bundler - -## Features - -1. **Security**: - - Encrypted storage (TweetNaCl secretbox) - - PBKDF2 key derivation (100k iterations) - - Password protection - - Local-only operations (no network calls) - -2. **Multi-Chain**: - - Solana (SVM) support - - Ethereum (EVM) support - - Proper derivation paths per chain - - Multiple accounts per chain - -3. **User Experience**: - - Clean, dark-themed UI - - Simple flows for creation/import - - Copy-to-clipboard for addresses - - Warning dialogs for sensitive operations - - Account switching - -## Files Created - -``` -packages/mobile-wallet/ -├── src/ -│ ├── App.tsx # Main UI (500+ lines) -│ ├── crypto/ -│ │ └── WalletCore.ts # Crypto core wrapper (300+ lines) -│ └── storage/ -│ └── SecureStorage.ts # Encrypted storage (40 lines) -├── android/ # Native Android project -├── ios/ # Native iOS project (future) -├── package.json # Dependencies -├── tsconfig.json # TypeScript config -├── metro.config.js # Metro bundler config -├── babel.config.js # Babel config -├── shim.js # Crypto polyfills -├── index.js # App entry point -├── app.json # App metadata -├── build-apk.sh # Build script -├── README.md # Documentation -└── SUMMARY.md # This file -``` - -## Differences from Main Backpack - -This wallet is intentionally minimal: - -**Excluded:** -- Extension/browser architecture -- Service workers -- NFT/xNFT support -- Token swaps -- DeFi integrations -- Notifications system -- Cloud sync -- Multiple UI frameworks -- Complex navigation -- Settings/preferences UI -- Network selection UI - -**Included (Core Only):** -- Mnemonic generation/import -- HD wallet derivation -- Transaction signing -- Message signing -- Account management -- Secure storage - -## Build Instructions - -```bash -# Navigate to package -cd packages/mobile-wallet - -# Install dependencies -yarn install - -# Build APK -./build-apk.sh -``` - -Output: `simple-crypto-wallet.apk` - -## Next Steps - -To use this wallet: - -1. Install APK on Android device -2. Create or import a wallet -3. Manage Solana and Ethereum accounts -4. Export private keys for use in other wallets - -To extend this wallet: - -1. Add network selection (mainnet/devnet) -2. Integrate RPC calls for balances -3. Add transaction building UI -4. Implement token transfers -5. Add transaction history -6. Support more chains (Polygon, BSC, etc.) - -## Notes - -- This is a standalone implementation from scratch -- No UI code reused from existing Backpack -- All crypto functions use the same core libraries as Backpack -- Designed for Android but can be adapted for iOS -- Educational and demonstrative in nature -- Production use would require additional security audits diff --git a/packages/mobile-wallet/android/app/build.gradle b/packages/mobile-wallet/android/app/build.gradle deleted file mode 100644 index cf1a8c3..0000000 --- a/packages/mobile-wallet/android/app/build.gradle +++ /dev/null @@ -1,77 +0,0 @@ -apply plugin: "com.android.application" -apply plugin: "org.jetbrains.kotlin.android" - -/** - * Set this to true to Run Proguard on Release builds to minify the Java bytecode. - */ -def enableProguardInReleaseBuilds = false - -/** - * The preferred build flavor of JavaScriptCore (JSC) - * - * For example, to use the international variant, you can use: - * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` - * - * The international variant includes ICU i18n library and necessary data - * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that - * give correct results when using with locales other than en-US. Note that - * this variant is about 6MiB larger per architecture than default. - */ -def jscFlavor = 'org.webkit:android-jsc:+' - -android { - ndkVersion rootProject.ext.ndkVersion - buildToolsVersion rootProject.ext.buildToolsVersion - compileSdk rootProject.ext.compileSdkVersion - - namespace "com.simplecryptowallet" - defaultConfig { - applicationId "com.simplecryptowallet" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = '17' - } - signingConfigs { - debug { - storeFile file('debug.keystore') - storePassword 'android' - keyAlias 'androiddebugkey' - keyPassword 'android' - } - } - buildTypes { - debug { - signingConfig signingConfigs.debug - } - release { - // Caution! In production, you need to generate your own keystore file. - // see https://reactnative.dev/docs/signed-apk-android. - signingConfig signingConfigs.debug - minifyEnabled enableProguardInReleaseBuilds - proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - } - } -} - -dependencies { - implementation("com.facebook.react:react-android:0.73.4") - - if (hermesEnabled.toBoolean()) { - implementation("com.facebook.react:hermes-android:0.73.4") - } else { - implementation jscFlavor - } - - // Required for react-native-keychain - implementation("androidx.biometric:biometric:1.1.0") -} diff --git a/packages/mobile-wallet/android/app/debug.keystore b/packages/mobile-wallet/android/app/debug.keystore deleted file mode 100644 index 364e105..0000000 Binary files a/packages/mobile-wallet/android/app/debug.keystore and /dev/null differ diff --git a/packages/mobile-wallet/android/app/proguard-rules.pro b/packages/mobile-wallet/android/app/proguard-rules.pro deleted file mode 100644 index 11b0257..0000000 --- a/packages/mobile-wallet/android/app/proguard-rules.pro +++ /dev/null @@ -1,10 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: diff --git a/packages/mobile-wallet/android/app/src/debug/AndroidManifest.xml b/packages/mobile-wallet/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index eb98c01..0000000 --- a/packages/mobile-wallet/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - diff --git a/packages/mobile-wallet/android/app/src/main/AndroidManifest.xml b/packages/mobile-wallet/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 4122f36..0000000 --- a/packages/mobile-wallet/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - diff --git a/packages/mobile-wallet/android/app/src/main/java/com/simplecryptowallet/MainActivity.kt b/packages/mobile-wallet/android/app/src/main/java/com/simplecryptowallet/MainActivity.kt deleted file mode 100644 index 14355c5..0000000 --- a/packages/mobile-wallet/android/app/src/main/java/com/simplecryptowallet/MainActivity.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.simplecryptowallet - -import com.facebook.react.ReactActivity -import com.facebook.react.ReactActivityDelegate -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled -import com.facebook.react.defaults.DefaultReactActivityDelegate - -class MainActivity : ReactActivity() { - - /** - * Returns the name of the main component registered from JavaScript. This is used to schedule - * rendering of the component. - */ - override fun getMainComponentName(): String = "SimpleCryptoWallet" - - /** - * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] - * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] - */ - override fun createReactActivityDelegate(): ReactActivityDelegate = - DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) -} diff --git a/packages/mobile-wallet/android/app/src/main/java/com/simplecryptowallet/MainApplication.kt b/packages/mobile-wallet/android/app/src/main/java/com/simplecryptowallet/MainApplication.kt deleted file mode 100644 index bad23dc..0000000 --- a/packages/mobile-wallet/android/app/src/main/java/com/simplecryptowallet/MainApplication.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.simplecryptowallet - -import android.app.Application -import com.facebook.react.ReactApplication -import com.facebook.react.ReactNativeHost -import com.facebook.react.ReactPackage -import com.facebook.react.shell.MainReactPackage -import com.facebook.soloader.SoLoader - -class MainApplication : Application(), ReactApplication { - - override val reactNativeHost: ReactNativeHost = object : ReactNativeHost(this) { - override fun getUseDeveloperSupport(): Boolean = true - - override fun getPackages(): List { - return listOf( - MainReactPackage() - ) - } - - override fun getJSMainModuleName(): String = "index" - } - - override fun onCreate() { - super.onCreate() - SoLoader.init(this, false) - } -} diff --git a/packages/mobile-wallet/android/app/src/main/res/drawable/rn_edit_text_material.xml b/packages/mobile-wallet/android/app/src/main/res/drawable/rn_edit_text_material.xml deleted file mode 100644 index 73b37e4..0000000 --- a/packages/mobile-wallet/android/app/src/main/res/drawable/rn_edit_text_material.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - diff --git a/packages/mobile-wallet/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/mobile-wallet/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a2f5908..0000000 Binary files a/packages/mobile-wallet/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/packages/mobile-wallet/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/packages/mobile-wallet/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 1b52399..0000000 Binary files a/packages/mobile-wallet/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/mobile-wallet/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/mobile-wallet/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index ff10afd..0000000 Binary files a/packages/mobile-wallet/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/packages/mobile-wallet/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/packages/mobile-wallet/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 115a4c7..0000000 Binary files a/packages/mobile-wallet/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/mobile-wallet/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/mobile-wallet/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index dcd3cd8..0000000 Binary files a/packages/mobile-wallet/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/mobile-wallet/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/packages/mobile-wallet/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 459ca60..0000000 Binary files a/packages/mobile-wallet/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/mobile-wallet/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/mobile-wallet/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8ca12fe..0000000 Binary files a/packages/mobile-wallet/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/mobile-wallet/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/packages/mobile-wallet/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8e19b41..0000000 Binary files a/packages/mobile-wallet/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/mobile-wallet/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/mobile-wallet/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index b824ebd..0000000 Binary files a/packages/mobile-wallet/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/packages/mobile-wallet/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/packages/mobile-wallet/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 4c19a13..0000000 Binary files a/packages/mobile-wallet/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/packages/mobile-wallet/android/app/src/main/res/values/strings.xml b/packages/mobile-wallet/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 1ecc7f6..0000000 --- a/packages/mobile-wallet/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - SimpleCryptoWallet - diff --git a/packages/mobile-wallet/android/app/src/main/res/values/styles.xml b/packages/mobile-wallet/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 7ba83a2..0000000 --- a/packages/mobile-wallet/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/packages/mobile-wallet/android/build.gradle b/packages/mobile-wallet/android/build.gradle deleted file mode 100644 index fd9d8e2..0000000 --- a/packages/mobile-wallet/android/build.gradle +++ /dev/null @@ -1,33 +0,0 @@ -buildscript { - ext { - buildToolsVersion = "34.0.0" - minSdkVersion = 21 - compileSdkVersion = 34 - targetSdkVersion = 34 - ndkVersion = "25.1.8937393" - kotlinVersion = "1.8.0" - } - repositories { - google() - mavenCentral() - } - dependencies { - classpath("com.android.tools.build:gradle:8.1.1") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") - } -} - -allprojects { - repositories { - google() - mavenCentral() - maven { - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - url("$rootDir/../node_modules/react-native/android") - } - maven { - // Android JSC is installed from npm - url("$rootDir/../node_modules/jsc-android/dist") - } - } -} diff --git a/packages/mobile-wallet/android/gradle.properties b/packages/mobile-wallet/android/gradle.properties deleted file mode 100644 index a46a5b9..0000000 --- a/packages/mobile-wallet/android/gradle.properties +++ /dev/null @@ -1,41 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m -org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true - -# Use this property to specify which architecture you want to build. -# You can also override it from the CLI using -# ./gradlew -PreactNativeArchitectures=x86_64 -reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 - -# Use this property to enable support to the new architecture. -# This will allow you to use TurboModules and the Fabric render in -# your application. You should enable this flag either if you want -# to write custom TurboModules/Fabric components OR use libraries that -# are providing them. -newArchEnabled=false - -# Use this property to enable or disable the Hermes JS engine. -# If set to false, you will be using JSC instead. -hermesEnabled=true diff --git a/packages/mobile-wallet/android/gradle/wrapper/gradle-wrapper.jar b/packages/mobile-wallet/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 7f93135..0000000 Binary files a/packages/mobile-wallet/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/packages/mobile-wallet/android/gradle/wrapper/gradle-wrapper.properties b/packages/mobile-wallet/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index d11cdd9..0000000 --- a/packages/mobile-wallet/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/packages/mobile-wallet/android/gradlew b/packages/mobile-wallet/android/gradlew deleted file mode 100755 index 0adc8e1..0000000 --- a/packages/mobile-wallet/android/gradlew +++ /dev/null @@ -1,249 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/packages/mobile-wallet/android/gradlew.bat b/packages/mobile-wallet/android/gradlew.bat deleted file mode 100644 index 6689b85..0000000 --- a/packages/mobile-wallet/android/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/packages/mobile-wallet/android/settings.gradle b/packages/mobile-wallet/android/settings.gradle deleted file mode 100644 index 2dc93a1..0000000 --- a/packages/mobile-wallet/android/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'SimpleCryptoWallet' -include ':app' diff --git a/packages/mobile-wallet/app.json b/packages/mobile-wallet/app.json deleted file mode 100644 index 7549743..0000000 --- a/packages/mobile-wallet/app.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "SimpleCryptoWallet", - "displayName": "Simple Crypto Wallet" -} diff --git a/packages/mobile-wallet/babel.config.js b/packages/mobile-wallet/babel.config.js deleted file mode 100644 index afb8da1..0000000 --- a/packages/mobile-wallet/babel.config.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - presets: ["module:metro-react-native-babel-preset"], - plugins: [ - ["@babel/plugin-proposal-class-properties", { loose: true }], - ["@babel/plugin-proposal-private-methods", { loose: true }], - [ - "module-resolver", - { - alias: { - crypto: "crypto-browserify", - stream: "stream-browserify", - buffer: "buffer", - }, - }, - ], - ], -}; diff --git a/packages/mobile-wallet/build-apk.sh b/packages/mobile-wallet/build-apk.sh deleted file mode 100755 index 61e27c7..0000000 --- a/packages/mobile-wallet/build-apk.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -set -e - -echo "Building Simple Crypto Wallet APK..." - -# Navigate to Android directory -cd android - -# Clean build -./gradlew clean - -# Build release APK -./gradlew assembleRelease - -# Copy APK to root directory -cp app/build/outputs/apk/release/app-release.apk ../simple-crypto-wallet.apk - -echo "APK built successfully: simple-crypto-wallet.apk" diff --git a/packages/mobile-wallet/index.js b/packages/mobile-wallet/index.js deleted file mode 100644 index 1f55326..0000000 --- a/packages/mobile-wallet/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import './shim'; -import { AppRegistry } from 'react-native'; -import DemoApp from './src/DemoApp'; -import { name as appName } from './app.json'; - -AppRegistry.registerComponent(appName, () => DemoApp); diff --git a/packages/mobile-wallet/metro.config.js b/packages/mobile-wallet/metro.config.js deleted file mode 100644 index 8776874..0000000 --- a/packages/mobile-wallet/metro.config.js +++ /dev/null @@ -1,26 +0,0 @@ -const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); -const path = require('path'); - -const config = { - transformer: { - getTransformOptions: async () => ({ - transform: { - experimentalImportSupport: false, - inlineRequires: true, - }, - }), - }, - resolver: { - extraNodeModules: { - crypto: require.resolve('crypto-browserify'), - stream: require.resolve('stream-browserify'), - buffer: require.resolve('buffer'), - }, - sourceExts: ['jsx', 'js', 'ts', 'tsx', 'json'], - }, - watchFolders: [ - path.resolve(__dirname, '../../node_modules'), - ], -}; - -module.exports = mergeConfig(getDefaultConfig(__dirname), config); diff --git a/packages/mobile-wallet/package.json b/packages/mobile-wallet/package.json deleted file mode 100644 index 0c48b12..0000000 --- a/packages/mobile-wallet/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "@coral-xyz/mobile-wallet", - "version": "0.1.0", - "private": true, - "main": "index.js", - "scripts": { - "android": "react-native run-android", - "ios": "react-native run-ios", - "start": "react-native start", - "build:android": "cd android && ./gradlew assembleRelease", - "lint": "eslint . --ext .js,.jsx,.ts,.tsx" - }, - "dependencies": { - "@coral-xyz/common": "workspace:*", - "@react-native-async-storage/async-storage": "^1.19.5", - "@react-native-community/masked-view": "^0.1.11", - "@react-navigation/native": "^6.1.9", - "@react-navigation/stack": "^6.3.20", - "@solana/web3.js": "1.63.1", - "bip32": "^2.0.6", - "bip39": "^3.0.4", - "bs58": "^5.0.0", - "buffer": "^6.0.3", - "crypto-browserify": "^3.12.0", - "ed25519-hd-key": "^1.2.0", - "ethers6": "npm:ethers@^6.8.0", - "react": "18.2.0", - "react-native": "0.73.4", - "react-native-crypto": "^2.2.0", - "react-native-gesture-handler": "^2.14.0", - "react-native-get-random-values": "^1.9.0", - "react-native-keychain": "^10.0.0", - "react-native-randombytes": "^3.6.1", - "react-native-safe-area-context": "^4.7.4", - "react-native-screens": "^3.27.0", - "react-native-svg": "^14.0.0", - "stream-browserify": "^3.0.0", - "tweetnacl": "^1.0.3" - }, - "devDependencies": { - "@babel/core": "^7.20.0", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/preset-env": "^7.20.0", - "@babel/runtime": "^7.20.0", - "@react-native/metro-config": "^0.73.4", - "@tsconfig/react-native": "^3.0.0", - "@types/react": "^18.0.24", - "@types/react-native": "^0.72.2", - "babel-plugin-module-resolver": "^5.0.0", - "metro-react-native-babel-preset": "0.76.8", - "typescript": "^5.3.3" - } -} diff --git a/packages/mobile-wallet/shim.js b/packages/mobile-wallet/shim.js deleted file mode 100644 index 59867ee..0000000 --- a/packages/mobile-wallet/shim.js +++ /dev/null @@ -1,11 +0,0 @@ -import 'react-native-get-random-values'; -import { Buffer } from 'buffer'; -global.Buffer = Buffer; - -// Crypto shims -import crypto from 'crypto-browserify'; -global.crypto = crypto; - -// Stream shim -import { Readable } from 'stream-browserify'; -global.Readable = Readable; diff --git a/packages/mobile-wallet/src/App.tsx b/packages/mobile-wallet/src/App.tsx deleted file mode 100644 index 8023cab..0000000 --- a/packages/mobile-wallet/src/App.tsx +++ /dev/null @@ -1,1298 +0,0 @@ -import React, { useState, useEffect, useCallback, useRef } from 'react'; -import { - SafeAreaView, - ScrollView, - StyleSheet, - Text, - TextInput, - TouchableOpacity, - View, - Alert, - Clipboard, - Switch, - Modal, - AppState, - AppStateStatus, -} from 'react-native'; -import 'react-native-get-random-values'; -import { Buffer } from 'buffer'; -global.Buffer = Buffer; - -import { WalletCore, WalletAccount } from './crypto/WalletCore'; -import { SecureStorage } from './storage/SecureStorage'; -import { AuthManager, PinLockoutError } from './storage/AuthManager'; - -type Screen = - | 'loading' - | 'welcome' - | 'create' - | 'import' - | 'securitySetup' - | 'unlock' - | 'wallet'; - -type SensitiveAction = - | { type: 'privateKey'; account: WalletAccount } - | { type: 'mnemonic' } - | { type: 'security-update' }; - -const AUTO_LOCK_TIMEOUT_MS = 2 * 60 * 1000; - -const App = (): JSX.Element => { - const [screen, setScreen] = useState('loading'); - const [password, setPassword] = useState(''); - const [confirmPassword, setConfirmPassword] = useState(''); - const [mnemonic, setMnemonic] = useState(''); - const [wallet, setWallet] = useState(null); - const [accounts, setAccounts] = useState([]); - const [selectedAccount, setSelectedAccount] = useState(null); - const [pin, setPin] = useState(''); - const [confirmPin, setConfirmPin] = useState(''); - const [biometricOptIn, setBiometricOptIn] = useState(false); - const [biometricSupported, setBiometricSupported] = useState(false); - const [biometricsEnabled, setBiometricsEnabled] = useState(false); - const [hasPinConfigured, setHasPinConfigured] = useState(false); - const [pinEntry, setPinEntry] = useState(''); - const [authMode, setAuthMode] = useState<'pin' | 'password'>('pin'); - const [lockoutDeadline, setLockoutDeadline] = useState(null); - const [lockoutSeconds, setLockoutSeconds] = useState(null); - const [pendingPassword, setPendingPassword] = useState(null); - const [reauthVisible, setReauthVisible] = useState(false); - const [reauthMode, setReauthMode] = useState<'pin' | 'password'>('pin'); - const [reauthPin, setReauthPin] = useState(''); - const [reauthPassword, setReauthPassword] = useState(''); - const [pendingSensitiveAction, setPendingSensitiveAction] = useState( - null - ); - const [secretModal, setSecretModal] = useState<{ title: string; value: string } | null>(null); - - const autoLockTimer = useRef | null>(null); - - const clearAutoLockTimer = useCallback(() => { - if (autoLockTimer.current) { - clearTimeout(autoLockTimer.current); - autoLockTimer.current = null; - } - }, []); - - const lockWallet = useCallback(() => { - setWallet(current => { - current?.clear(); - return null; - }); - setAccounts([]); - setSelectedAccount(null); - setPendingPassword(null); - setPinEntry(''); - setReauthVisible(false); - setPendingSensitiveAction(null); - setSecretModal(null); - setReauthPin(''); - setReauthPassword(''); - setMnemonic(''); - setPassword(''); - setConfirmPassword(''); - setPin(''); - setConfirmPin(''); - setBiometricOptIn(false); - clearAutoLockTimer(); - setScreen('unlock'); - }, [clearAutoLockTimer]); - - const recordActivity = useCallback(() => { - if (screen !== 'wallet') { - clearAutoLockTimer(); - return; - } - if (autoLockTimer.current) { - clearTimeout(autoLockTimer.current); - } - autoLockTimer.current = setTimeout(() => { - lockWallet(); - }, AUTO_LOCK_TIMEOUT_MS); - }, [clearAutoLockTimer, lockWallet, screen]); - - useEffect(() => { - return () => { - clearAutoLockTimer(); - }; - }, [clearAutoLockTimer]); - - useEffect(() => { - const initializeSecurity = async () => { - const supported = await AuthManager.isBiometricSupported(); - setBiometricSupported(supported); - await refreshAuthState(); - }; - initializeSecurity(); - }, []); - - useEffect(() => { - setAuthMode(hasPinConfigured ? 'pin' : 'password'); - }, [hasPinConfigured]); - - useEffect(() => { - if (screen === 'wallet') { - recordActivity(); - } - }, [screen, recordActivity]); - - useEffect(() => { - if (!lockoutDeadline) { - setLockoutSeconds(null); - return; - } - - const updateRemaining = () => { - const remainingMs = lockoutDeadline - Date.now(); - if (remainingMs <= 0) { - setLockoutDeadline(null); - setLockoutSeconds(null); - } else { - setLockoutSeconds(Math.ceil(remainingMs / 1000)); - } - }; - - updateRemaining(); - const interval = setInterval(updateRemaining, 1000); - return () => clearInterval(interval); - }, [lockoutDeadline]); - - useEffect(() => { - const sub = AppState.addEventListener('change', (nextState: AppStateStatus) => { - if ( - nextState !== 'active' && - (screen === 'wallet' || screen === 'create' || screen === 'import' || screen === 'securitySetup') - ) { - lockWallet(); - } else if (nextState === 'active') { - recordActivity(); - } - }); - return () => sub.remove(); - }, [lockWallet, recordActivity, screen]); - - const refreshAuthState = async (preserveScreen = false) => { - const hasWallet = await SecureStorage.hasWallet(); - const pinConfigured = await AuthManager.hasPin(); - const biometricsConfigured = await AuthManager.isBiometricEnabled(); - - setHasPinConfigured(pinConfigured); - setBiometricsEnabled(biometricsConfigured); - - if (!preserveScreen) { - if (hasWallet) { - setScreen('unlock'); - } else { - setScreen('welcome'); - } - } - }; - - const handleCreateWallet = () => { - const newWallet = new WalletCore(); - const newMnemonic = newWallet.generateWallet(); - setMnemonic(newMnemonic); - setWallet(newWallet); - setScreen('create'); - }; - - const handleRevealRecoveryPhrase = () => { - if (!wallet) return; - - Alert.alert( - 'Sensitive Information', - 'Your recovery phrase controls your assets. Unlock to reveal it.', - [ - { - text: 'Reveal Phrase', - style: 'destructive', - onPress: () => beginSensitiveAction({ type: 'mnemonic' }), - }, - { text: 'Cancel', style: 'cancel' }, - ] - ); - }; - - const handleManageSecurity = () => { - if (!wallet) return; - beginSensitiveAction({ type: 'security-update' }); - }; - - const validatePinInputs = (): boolean => { - if (!/^\d{6}$/.test(pin)) { - Alert.alert('Error', 'PIN must be a 6-digit number'); - return false; - } - if (pin !== confirmPin) { - Alert.alert('Error', 'PINs do not match'); - return false; - } - return true; - }; - - const configureSecurity = async (activePassword: string) => { - try { - await AuthManager.setupPin(pin, activePassword); - if (biometricSupported && biometricOptIn) { - await AuthManager.enableBiometrics(activePassword); - setBiometricsEnabled(true); - } else { - await AuthManager.disableBiometrics(); - setBiometricsEnabled(false); - } - setHasPinConfigured(true); - setPin(''); - setConfirmPin(''); - setBiometricOptIn(false); - } catch (error: any) { - if (error instanceof Error) { - throw error; - } - throw new Error('Failed to configure security'); - } - }; - - const hydrateWalletFromMnemonic = (seedPhrase: string) => { - const loadedWallet = new WalletCore(); - loadedWallet.importWallet(seedPhrase); - setWallet(loadedWallet); - setAccounts(loadedWallet.getAccounts()); - setSelectedAccount(loadedWallet.getAccounts()[0]); - }; - - const unlockWithSecret = async (secret: string) => { - const loadedMnemonic = await SecureStorage.loadWallet(secret); - hydrateWalletFromMnemonic(loadedMnemonic); - setPinEntry(''); - if (!hasPinConfigured) { - setPendingPassword(secret); - setPin(''); - setConfirmPin(''); - setBiometricOptIn(biometricsEnabled); - setScreen('securitySetup'); - } else { - setPendingPassword(null); - setScreen('wallet'); - recordActivity(); - } - }; - - const handleSaveWallet = async () => { - if (!validatePinInputs()) { - return; - } - if (password !== confirmPassword) { - Alert.alert('Error', 'Passwords do not match'); - return; - } - if (password.length < 8) { - Alert.alert('Error', 'Password must be at least 8 characters'); - return; - } - - try { - await SecureStorage.saveWallet(mnemonic, password); - await configureSecurity(password); - setAccounts(wallet?.getAccounts() || []); - setSelectedAccount(wallet?.getAccounts()[0] || null); - setPassword(''); - setConfirmPassword(''); - setScreen('wallet'); - recordActivity(); - } catch (error: any) { - Alert.alert('Error', error?.message || 'Failed to save wallet'); - } - }; - - const handleImportWallet = async () => { - if (!validatePinInputs()) { - return; - } - if (password !== confirmPassword) { - Alert.alert('Error', 'Passwords do not match'); - return; - } - if (password.length < 8) { - Alert.alert('Error', 'Password must be at least 8 characters'); - return; - } - - try { - const newWallet = new WalletCore(); - newWallet.importWallet(mnemonic.trim()); - await SecureStorage.saveWallet(mnemonic.trim(), password); - await configureSecurity(password); - setWallet(newWallet); - setAccounts(newWallet.getAccounts()); - setSelectedAccount(newWallet.getAccounts()[0]); - setPassword(''); - setConfirmPassword(''); - setMnemonic(''); - setScreen('wallet'); - recordActivity(); - } catch (error: any) { - Alert.alert('Error', error?.message || 'Invalid mnemonic phrase'); - } - }; - - const handlePasswordUnlock = async () => { - if (!password) { - Alert.alert('Error', 'Enter your master password'); - return; - } - try { - const secret = password; - await unlockWithSecret(secret); - setPassword(''); - } catch (error: any) { - Alert.alert('Error', error?.message || 'Incorrect password'); - } - }; - - const handlePinUnlock = async () => { - if (!/^\d{6}$/.test(pinEntry)) { - Alert.alert('Error', 'Enter your 6-digit PIN'); - return; - } - try { - const secret = await AuthManager.unlockWithPin(pinEntry); - await unlockWithSecret(secret); - setLockoutDeadline(null); - setLockoutSeconds(null); - setPinEntry(''); - } catch (error: any) { - if (error instanceof PinLockoutError) { - setLockoutDeadline(Date.now() + error.remainingMs); - Alert.alert( - 'Too many attempts', - `Wallet is locked. Try again in ${Math.ceil(error.remainingMs / 1000)} seconds.` - ); - return; - } - Alert.alert('Error', error?.message || 'Invalid PIN'); - } - }; - - const handleBiometricUnlock = async () => { - try { - const secret = await AuthManager.unlockWithBiometrics(); - await unlockWithSecret(secret); - } catch (error: any) { - Alert.alert('Error', error?.message || 'Biometric authentication failed'); - } - }; - - const handleSecuritySetupSubmit = async () => { - if (!pendingPassword) { - Alert.alert('Error', 'Missing master password context'); - return; - } - if (!validatePinInputs()) { - return; - } - try { - await configureSecurity(pendingPassword); - setPendingPassword(null); - await refreshAuthState(true); - setScreen('wallet'); - recordActivity(); - } catch (error: any) { - Alert.alert('Error', error?.message || 'Failed to enable security'); - } - }; - - const handleLogout = () => { - lockWallet(); - }; - - const handleAddAccount = () => { - if (!wallet) return; - - Alert.alert('Add Account', 'Choose blockchain', [ - { - text: 'Solana', - onPress: () => { - const account = wallet.addAccount('solana'); - setAccounts(wallet.getAccounts()); - recordActivity(); - Alert.alert('Success', `Created Solana account:\n${account.publicKey}`); - }, - }, - { - text: 'Ethereum', - onPress: () => { - const account = wallet.addAccount('ethereum'); - setAccounts(wallet.getAccounts()); - recordActivity(); - Alert.alert('Success', `Created Ethereum account:\n${account.publicKey}`); - }, - }, - { text: 'Cancel', style: 'cancel' }, - ]); - }; - - const beginSensitiveAction = (action: SensitiveAction) => { - setPendingSensitiveAction(action); - setReauthMode(hasPinConfigured ? 'pin' : 'password'); - setReauthVisible(true); - setReauthPin(''); - setReauthPassword(''); - recordActivity(); - }; - - const closeReauthModal = (clearAction = true) => { - setReauthVisible(false); - if (clearAction) { - setPendingSensitiveAction(null); - } - setReauthPin(''); - setReauthPassword(''); - }; - - const completeSensitiveAction = (passwordContext?: string) => { - if (!pendingSensitiveAction || !wallet) { - closeReauthModal(); - return; - } - - const action = pendingSensitiveAction; - closeReauthModal(false); - setPendingSensitiveAction(null); - - if (action.type === 'privateKey') { - const privateKey = wallet.getPrivateKey(action.account.publicKey); - setSecretModal({ title: 'Private Key', value: privateKey }); - recordActivity(); - return; - } - - if (action.type === 'mnemonic') { - const phrase = wallet.getMnemonic(); - setSecretModal({ title: 'Recovery Phrase', value: phrase }); - recordActivity(); - return; - } - - if (action.type === 'security-update') { - if (!passwordContext) { - Alert.alert('Error', 'Authentication failed. Unable to update security.'); - closeReauthModal(); - return; - } - setPendingPassword(passwordContext); - setPin(''); - setConfirmPin(''); - setBiometricOptIn(biometricsEnabled); - setScreen('securitySetup'); - recordActivity(); - } - }; - - const handleSensitivePinConfirm = async () => { - if (!/^\d{6}$/.test(reauthPin)) { - Alert.alert('Error', 'Enter your 6-digit PIN'); - return; - } - try { - const secret = await AuthManager.unlockWithPin(reauthPin); - setLockoutDeadline(null); - setLockoutSeconds(null); - completeSensitiveAction(secret); - } catch (error: any) { - if (error instanceof PinLockoutError) { - setLockoutDeadline(Date.now() + error.remainingMs); - Alert.alert( - 'Too many attempts', - `Wallet is locked. Try again in ${Math.ceil(error.remainingMs / 1000)} seconds.` - ); - return; - } - Alert.alert('Error', error?.message || 'Invalid PIN'); - } - }; - - const handleSensitivePasswordConfirm = async () => { - if (!reauthPassword) { - Alert.alert('Error', 'Enter your master password'); - return; - } - try { - await SecureStorage.loadWallet(reauthPassword); - completeSensitiveAction(reauthPassword); - } catch (error: any) { - Alert.alert('Error', error?.message || 'Incorrect password'); - } - }; - - const handleSensitiveBiometricConfirm = async () => { - try { - const secret = await AuthManager.unlockWithBiometrics(); - completeSensitiveAction(secret); - } catch (error: any) { - Alert.alert('Error', error?.message || 'Biometric authentication failed'); - } - }; - - const getReauthDescription = () => { - if (!pendingSensitiveAction) { - return ''; - } - switch (pendingSensitiveAction.type) { - case 'privateKey': - return 'Confirm your identity to reveal this private key.'; - case 'mnemonic': - return 'Authentication is required to reveal your recovery phrase.'; - case 'security-update': - return 'Authenticate to update your PIN and biometric preferences.'; - default: - return ''; - } - }; - - const handleCopySecret = () => { - if (!secretModal) { - return; - } - Clipboard.setString(secretModal.value); - Alert.alert('Copied', `${secretModal.title} copied to clipboard`); - recordActivity(); - }; - - const closeSecretModal = () => { - setSecretModal(null); - recordActivity(); - }; - - const renderReauthModal = () => ( - closeReauthModal()}> - - - Confirm Identity - {getReauthDescription()} - {reauthMode === 'pin' ? ( - <> - - - Continue - - - ) : ( - <> - - - Continue - - - )} - {biometricsEnabled && ( - - Use Biometrics - - )} - setReauthMode(reauthMode === 'pin' ? 'password' : 'pin')}> - - {reauthMode === 'pin' ? 'Use master password instead' : 'Use PIN instead'} - - - closeReauthModal()}> - Cancel - - - - - ); - - const renderSecretModal = () => ( - - - - {secretModal?.title} - - Never share this information. Anyone with it can control your funds. - - - - {secretModal?.value} - - - - Copy - - - Close - - - - - ); - - const handleShowPrivateKey = (account: WalletAccount) => { - if (!wallet) return; - - Alert.alert( - 'Warning', - 'Never share your private key. Anyone with this key can access your funds.', - [ - { - text: 'Continue', - style: 'destructive', - onPress: () => beginSensitiveAction({ type: 'privateKey', account }), - }, - { text: 'Cancel', style: 'cancel' }, - ] - ); - }; - - const copyToClipboard = (text: string) => { - recordActivity(); - Clipboard.setString(text); - Alert.alert('Copied', 'Address copied to clipboard'); - }; - - // Welcome Screen - if (screen === 'welcome') { - return ( - - - Simple Crypto Wallet - Get started with your wallet - - Create New Wallet - - setScreen('import')}> - Import Wallet - - - - ); - } - - // Create Wallet Screen - if (screen === 'create') { - return ( - - - Your Recovery Phrase - - Write down these words in order and keep them safe. This is the ONLY way to - recover your wallet. - - - {mnemonic} - - copyToClipboard(mnemonic)}> - Copy to Clipboard - - - Set Password - - - - App PIN (6 digits) - - - {biometricSupported && ( - - - Enable biometrics - - Use Face ID or fingerprint to unlock quickly. - - - - - )} - - - Save Wallet - - - - ); - } - - // Import Wallet Screen - if (screen === 'import') { - return ( - - - Import Wallet - Recovery Phrase - - - Set Password - - - - App PIN (6 digits) - - - {biometricSupported && ( - - - Enable biometrics - - Use Face ID or fingerprint to unlock quickly. - - - - - )} - - - Import Wallet - - setScreen('welcome')}> - Back - - - - ); - } - - if (screen === 'securitySetup') { - return ( - - - Secure Your Wallet - - Set a PIN to unlock your wallet quickly. Biometrics are optional but recommended. - - - App PIN (6 digits) - - - - {biometricSupported && ( - - - Enable biometrics - - Use Face ID or fingerprint for faster unlocks. - - - - - )} - - - Enable Protection - - - - ); - } - - // Unlock Screen - if (screen === 'unlock') { - return ( - - - Welcome Back - {authMode === 'pin' && hasPinConfigured ? ( - <> - - {lockoutSeconds - ? `Too many attempts. Try again in ${lockoutSeconds}s` - : 'Enter PIN'} - - - - Unlock - - - ) : ( - <> - Enter Password - - - Unlock - - - )} - {biometricsEnabled && ( - - Use Biometrics - - )} - {hasPinConfigured && ( - setAuthMode(authMode === 'pin' ? 'password' : 'pin')}> - - {authMode === 'pin' ? 'Use master password instead' : 'Use PIN instead'} - - - )} - - - ); - } - - // Wallet Screen - if (screen === 'wallet' && selectedAccount) { - return ( - <> - - - - My Wallet - - Logout - - - - - Active Account - - - {selectedAccount.blockchain.toUpperCase()} - - copyToClipboard(selectedAccount.publicKey)}> - - {selectedAccount.publicKey} - - - - - - handleShowPrivateKey(selectedAccount)}> - Show Private Key - - - - Show Recovery Phrase - - - - All Accounts - {accounts.map((account, index) => ( - setSelectedAccount(account)}> - - {account.blockchain.toUpperCase()} - - - {account.publicKey} - - {account.derivationPath} - - ))} - - - - Security - Manage your PIN and biometrics. - - Update PIN & Biometrics - - - - - + Add Account - - - - {renderReauthModal()} - {renderSecretModal()} - - ); - } - - return ( - - - Loading... - - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#1a1a2e', - }, - scrollView: { - flex: 1, - padding: 20, - }, - centered: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - padding: 20, - }, - header: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: 20, - }, - title: { - fontSize: 28, - fontWeight: 'bold', - color: '#fff', - marginBottom: 10, - }, - subtitle: { - fontSize: 16, - color: '#aaa', - marginBottom: 40, - }, - label: { - fontSize: 14, - color: '#aaa', - marginBottom: 8, - marginTop: 16, - }, - input: { - backgroundColor: '#16213e', - color: '#fff', - padding: 15, - borderRadius: 10, - fontSize: 16, - marginBottom: 12, - borderWidth: 1, - borderColor: '#0f3460', - }, - mnemonicInput: { - height: 80, - textAlignVertical: 'top', - }, - button: { - backgroundColor: '#0f3460', - padding: 16, - borderRadius: 10, - alignItems: 'center', - marginTop: 12, - }, - disabledButton: { - opacity: 0.5, - }, - buttonSecondary: { - backgroundColor: '#16213e', - }, - buttonText: { - color: '#fff', - fontSize: 16, - fontWeight: '600', - }, - dangerButton: { - backgroundColor: '#c70039', - padding: 16, - borderRadius: 10, - alignItems: 'center', - marginTop: 12, - }, - copyButton: { - backgroundColor: '#16213e', - padding: 12, - borderRadius: 10, - alignItems: 'center', - marginTop: 12, - }, - linkButton: { - marginTop: 16, - }, - linkText: { - color: '#4ecca3', - fontSize: 14, - textDecorationLine: 'underline', - }, - logoutButton: { - padding: 8, - }, - logoutText: { - color: '#c70039', - fontSize: 14, - fontWeight: '600', - }, - mnemonicContainer: { - backgroundColor: '#16213e', - padding: 20, - borderRadius: 10, - marginTop: 12, - }, - mnemonic: { - color: '#fff', - fontSize: 16, - lineHeight: 24, - }, - warning: { - color: '#ff6b6b', - fontSize: 14, - marginTop: 12, - textAlign: 'center', - }, - toggleRow: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: '#16213e', - padding: 12, - borderRadius: 10, - marginTop: 12, - }, - toggleContent: { - flex: 1, - marginRight: 16, - }, - toggleLabel: { - color: '#fff', - fontSize: 16, - fontWeight: '600', - }, - toggleHint: { - color: '#aaa', - fontSize: 12, - marginTop: 4, - }, - modalBackdrop: { - flex: 1, - backgroundColor: 'rgba(0, 0, 0, 0.7)', - justifyContent: 'center', - alignItems: 'center', - padding: 20, - }, - modalContent: { - width: '100%', - backgroundColor: '#1f1f3a', - borderRadius: 16, - padding: 24, - }, - modalTitle: { - color: '#fff', - fontSize: 20, - fontWeight: '700', - marginBottom: 8, - }, - modalSubtitle: { - color: '#ccc', - fontSize: 14, - marginBottom: 16, - }, - secretScrollView: { - maxHeight: 200, - marginBottom: 16, - }, - secretValue: { - color: '#fff', - fontSize: 16, - lineHeight: 24, - }, - accountSelector: { - backgroundColor: '#16213e', - padding: 16, - borderRadius: 10, - marginBottom: 12, - }, - accountInfo: { - marginTop: 8, - }, - blockchain: { - color: '#4ecca3', - fontSize: 12, - fontWeight: '600', - marginBottom: 4, - }, - address: { - color: '#fff', - fontSize: 14, - }, - section: { - marginTop: 24, - }, - sectionTitle: { - fontSize: 18, - fontWeight: '600', - color: '#fff', - marginBottom: 12, - }, - accountCard: { - backgroundColor: '#16213e', - padding: 16, - borderRadius: 10, - marginBottom: 8, - borderWidth: 2, - borderColor: 'transparent', - }, - accountCardActive: { - borderColor: '#0f3460', - }, - accountBlockchain: { - color: '#4ecca3', - fontSize: 12, - fontWeight: '600', - marginBottom: 4, - }, - accountAddress: { - color: '#fff', - fontSize: 14, - marginBottom: 4, - }, - accountPath: { - color: '#888', - fontSize: 12, - }, -}); - -export default App; diff --git a/packages/mobile-wallet/src/DemoApp.tsx b/packages/mobile-wallet/src/DemoApp.tsx deleted file mode 100644 index 6c2f6c4..0000000 --- a/packages/mobile-wallet/src/DemoApp.tsx +++ /dev/null @@ -1,328 +0,0 @@ -import React, { useState } from 'react'; -import { - SafeAreaView, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, - Alert, -} from 'react-native'; -import 'react-native-get-random-values'; -import { Buffer } from 'buffer'; -global.Buffer = Buffer; - -import { WalletCore } from './crypto/WalletCore'; - -const DemoApp = (): JSX.Element => { - const [wallet] = useState(new WalletCore()); - const [mnemonic, setMnemonic] = useState(''); - const [accounts, setAccounts] = useState([]); - const [testResult, setTestResult] = useState(''); - - const handleGenerateWallet = () => { - try { - const newMnemonic = wallet.generateWallet(); - setMnemonic(newMnemonic); - const accts = wallet.getAccounts(); - setAccounts(accts); - setTestResult('✓ Wallet generated successfully!'); - } catch (error: any) { - setTestResult('✗ Error: ' + error.message); - } - }; - - const handleAddSolanaAccount = () => { - try { - const account = wallet.addAccount('solana'); - setAccounts(wallet.getAccounts()); - setTestResult(`✓ Added Solana account: ${account.publicKey.substring(0, 20)}...`); - } catch (error: any) { - setTestResult('✗ Error: ' + error.message); - } - }; - - const handleAddEthereumAccount = () => { - try { - const account = wallet.addAccount('ethereum'); - setAccounts(wallet.getAccounts()); - setTestResult(`✓ Added Ethereum account: ${account.publicKey}`); - } catch (error: any) { - setTestResult('✗ Error: ' + error.message); - } - }; - - const handleGetPrivateKey = (publicKey: string) => { - try { - const privateKey = wallet.getPrivateKey(publicKey); - Alert.alert( - 'Private Key', - `${privateKey.substring(0, 40)}...`, - [{ text: 'OK' }] - ); - setTestResult('✓ Private key retrieved'); - } catch (error: any) { - setTestResult('✗ Error: ' + error.message); - } - }; - - const handleClearWallet = () => { - wallet.clear(); - setMnemonic(''); - setAccounts([]); - setTestResult('✓ Wallet cleared'); - }; - - return ( - - - Backpack Crypto Demo - Testing Core Wallet Features - - {/* Generate Wallet Section */} - - 1. Generate Wallet - - Generate New Wallet - - - {mnemonic ? ( - - Mnemonic (12 words): - {mnemonic} - - ) : null} - - - {/* Accounts Section */} - {accounts.length > 0 && ( - - 2. Accounts ({accounts.length}) - {accounts.map((account, index) => ( - - - {account.blockchain.toUpperCase()} - - - {account.publicKey} - - {account.derivationPath} - handleGetPrivateKey(account.publicKey)}> - Show Private Key - - - ))} - - )} - - {/* Add Accounts Section */} - {mnemonic && ( - - 3. Add More Accounts - - - + Solana - - - + Ethereum - - - - )} - - {/* Test Result */} - {testResult && ( - - Result - - {testResult} - - - )} - - {/* Clear Section */} - {mnemonic && ( - - - Clear Wallet - - - )} - - {/* Features List */} - - Backpack Features Tested - - ✓ BIP39 Mnemonic Generation - ✓ BIP44 HD Derivation - ✓ Solana (ed25519) Support - ✓ Ethereum (secp256k1) Support - ✓ Multi-Account Management - ✓ Private Key Export - ✓ TweetNaCl Encryption - ✓ Secure Key Storage - - - - - - Powered by Backpack Crypto Core - - - All operations happen locally on your device - - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#0a0a0f', - }, - scrollView: { - flex: 1, - padding: 20, - }, - title: { - fontSize: 32, - fontWeight: 'bold', - color: '#fff', - textAlign: 'center', - marginBottom: 8, - }, - subtitle: { - fontSize: 16, - color: '#888', - textAlign: 'center', - marginBottom: 30, - }, - section: { - marginBottom: 24, - }, - sectionTitle: { - fontSize: 18, - fontWeight: '600', - color: '#4ecca3', - marginBottom: 12, - }, - button: { - backgroundColor: '#1e3a5f', - padding: 16, - borderRadius: 12, - alignItems: 'center', - marginBottom: 12, - }, - buttonHalf: { - flex: 1, - marginHorizontal: 6, - }, - buttonRow: { - flexDirection: 'row', - justifyContent: 'space-between', - }, - dangerButton: { - backgroundColor: '#5f1e1e', - }, - buttonText: { - color: '#fff', - fontSize: 16, - fontWeight: '600', - }, - smallButton: { - backgroundColor: '#2a2a40', - padding: 10, - borderRadius: 8, - marginTop: 8, - }, - smallButtonText: { - color: '#4ecca3', - fontSize: 14, - textAlign: 'center', - }, - label: { - fontSize: 12, - color: '#888', - marginBottom: 6, - }, - resultBox: { - backgroundColor: '#1a1a2e', - padding: 16, - borderRadius: 10, - borderWidth: 1, - borderColor: '#2a2a40', - }, - mnemonicText: { - color: '#fff', - fontSize: 14, - lineHeight: 22, - }, - accountCard: { - backgroundColor: '#1a1a2e', - padding: 16, - borderRadius: 10, - marginBottom: 12, - borderWidth: 1, - borderColor: '#2a2a40', - }, - accountType: { - color: '#4ecca3', - fontSize: 12, - fontWeight: '600', - marginBottom: 6, - }, - accountAddress: { - color: '#fff', - fontSize: 13, - marginBottom: 4, - fontFamily: 'monospace', - }, - accountPath: { - color: '#666', - fontSize: 11, - fontFamily: 'monospace', - }, - resultText: { - color: '#fff', - fontSize: 14, - }, - featuresList: { - backgroundColor: '#1a1a2e', - padding: 16, - borderRadius: 10, - borderWidth: 1, - borderColor: '#2a2a40', - }, - featureItem: { - color: '#4ecca3', - fontSize: 14, - marginBottom: 8, - paddingLeft: 8, - }, - footer: { - marginTop: 40, - marginBottom: 40, - alignItems: 'center', - }, - footerText: { - color: '#888', - fontSize: 14, - marginBottom: 4, - }, - footerSubtext: { - color: '#666', - fontSize: 12, - }, -}); - -export default DemoApp; diff --git a/packages/mobile-wallet/src/crypto/WalletCore.ts b/packages/mobile-wallet/src/crypto/WalletCore.ts deleted file mode 100644 index 62e316c..0000000 --- a/packages/mobile-wallet/src/crypto/WalletCore.ts +++ /dev/null @@ -1,298 +0,0 @@ -import { Keypair } from '@solana/web3.js'; -import { mnemonicToSeedSync, generateMnemonic, validateMnemonic } from 'bip39'; -import { encode, decode } from 'bs58'; -import { HDNodeWallet, Wallet } from 'ethers6'; -import nacl from 'tweetnacl'; -import { derivePath } from 'ed25519-hd-key'; -import { randomBytes, secretbox } from 'tweetnacl'; -//@ts-ignore -import Crypto from 'crypto-browserify'; - -// Types -export interface WalletAccount { - publicKey: string; - blockchain: 'solana' | 'ethereum'; - derivationPath: string; -} - -export interface SecretPayload { - ciphertext: string; - nonce: string; - salt: string; - kdf: string; - iterations: number; - digest: string; -} - -// Encryption functions -export async function encrypt( - plaintext: string, - password: string -): Promise { - const salt = randomBytes(16); - const kdf = 'pbkdf2'; - const iterations = 100000; // Mobile optimized - const digest = 'sha256'; - - const key = await new Promise((resolve, reject) => - Crypto.pbkdf2( - password, - salt, - iterations, - secretbox.keyLength, - digest, - (err: Error, key: Buffer) => (err ? reject(err) : resolve(key)) - ) - ); - - const nonce = randomBytes(secretbox.nonceLength); - const ciphertext = secretbox(Buffer.from(plaintext), nonce, key); - - return { - ciphertext: encode(ciphertext), - nonce: encode(nonce), - kdf, - salt: encode(salt), - iterations, - digest, - }; -} - -export async function decrypt( - cipherObj: SecretPayload, - password: string -): Promise { - const { - ciphertext: encodedCiphertext, - nonce: encodedNonce, - salt: encodedSalt, - iterations, - digest, - } = cipherObj; - - const ciphertext = decode(encodedCiphertext); - const nonce = decode(encodedNonce); - const salt = decode(encodedSalt); - - const key = await new Promise((resolve, reject) => - Crypto.pbkdf2( - password, - salt, - iterations, - secretbox.keyLength, - digest, - (err: Error, key: Buffer) => (err ? reject(err) : resolve(key)) - ) - ); - - const plaintext = secretbox.open(ciphertext, nonce, key); - if (!plaintext) { - throw new Error('Incorrect password'); - } - - return Buffer.from(plaintext).toString(); -} - -// Solana functions -export function deriveSolanaKeypair(seed: Buffer, derivationPath: string): Keypair { - const derivedSeed = derivePath(derivationPath, seed.toString('hex')).key; - return Keypair.fromSeed(derivedSeed); -} - -export function deriveSolanaPrivateKey(seed: Buffer, derivationPath: string): Uint8Array { - const keypair = deriveSolanaKeypair(seed, derivationPath); - return keypair.secretKey; -} - -export function getSolanaKeypair(secretKey: string): Keypair { - try { - // Try bs58 format first - return Keypair.fromSecretKey(decode(secretKey)); - } catch { - // Try hex format - return Keypair.fromSecretKey(Buffer.from(secretKey, 'hex')); - } -} - -export async function signSolanaTransaction( - tx: Buffer, - secretKey: string -): Promise { - const keypair = getSolanaKeypair(secretKey); - return encode(nacl.sign.detached(new Uint8Array(tx), keypair.secretKey)); -} - -export async function signSolanaMessage( - message: Buffer, - secretKey: string -): Promise { - const keypair = getSolanaKeypair(secretKey); - return encode(nacl.sign.detached(new Uint8Array(message), keypair.secretKey)); -} - -// Ethereum functions -export function deriveEthereumWallet(seed: Buffer, derivationPath: string): HDNodeWallet { - const hdNode = HDNodeWallet.fromSeed(seed); - return hdNode.derivePath(derivationPath); -} - -export function deriveEthereumPrivateKey(seed: Buffer, derivationPath: string): string { - const wallet = deriveEthereumWallet(seed, derivationPath); - return wallet.privateKey; -} - -export function getEthereumWallet(secretKey: string): Wallet { - return new Wallet(secretKey); -} - -export async function signEthereumTransaction( - serializedTx: string, - secretKey: string -): Promise { - const wallet = new Wallet(secretKey); - return await wallet.signTransaction(JSON.parse(serializedTx)); -} - -export async function signEthereumMessage( - message: string, - secretKey: string -): Promise { - const wallet = new Wallet(secretKey); - return await wallet.signMessage(message); -} - -// Wallet management -export class WalletCore { - private mnemonic: string | null = null; - private seed: Buffer | null = null; - private accounts: WalletAccount[] = []; - - // Generate new wallet - generateWallet(): string { - this.mnemonic = generateMnemonic(128); // 12 words - this.seed = mnemonicToSeedSync(this.mnemonic); - - // Generate default accounts - this.accounts = [ - { - publicKey: this.deriveSolanaAccount("m/44'/501'/0'/0'").publicKey.toString(), - blockchain: 'solana', - derivationPath: "m/44'/501'/0'/0'", - }, - { - publicKey: this.deriveEthereumAccount("m/44'/60'/0'/0/0").address, - blockchain: 'ethereum', - derivationPath: "m/44'/60'/0'/0/0", - }, - ]; - - return this.mnemonic; - } - - // Import wallet from mnemonic - importWallet(mnemonic: string): void { - if (!validateMnemonic(mnemonic)) { - throw new Error('Invalid mnemonic'); - } - - this.mnemonic = mnemonic; - this.seed = mnemonicToSeedSync(mnemonic); - - // Restore default accounts - this.accounts = [ - { - publicKey: this.deriveSolanaAccount("m/44'/501'/0'/0'").publicKey.toString(), - blockchain: 'solana', - derivationPath: "m/44'/501'/0'/0'", - }, - { - publicKey: this.deriveEthereumAccount("m/44'/60'/0'/0/0").address, - blockchain: 'ethereum', - derivationPath: "m/44'/60'/0'/0/0", - }, - ]; - } - - // Get mnemonic (must be unlocked) - getMnemonic(): string { - if (!this.mnemonic) { - throw new Error('Wallet not initialized'); - } - return this.mnemonic; - } - - // Get accounts - getAccounts(): WalletAccount[] { - return this.accounts; - } - - // Derive Solana account - private deriveSolanaAccount(path: string): Keypair { - if (!this.seed) { - throw new Error('Wallet not initialized'); - } - return deriveSolanaKeypair(this.seed, path); - } - - // Derive Ethereum account - private deriveEthereumAccount(path: string): HDNodeWallet { - if (!this.seed) { - throw new Error('Wallet not initialized'); - } - return deriveEthereumWallet(this.seed, path); - } - - // Get private key for account - getPrivateKey(publicKey: string): string { - const account = this.accounts.find(a => a.publicKey === publicKey); - if (!account || !this.seed) { - throw new Error('Account not found or wallet not initialized'); - } - - if (account.blockchain === 'solana') { - const keypair = deriveSolanaKeypair(this.seed, account.derivationPath); - return encode(keypair.secretKey); - } else { - const wallet = deriveEthereumWallet(this.seed, account.derivationPath); - return wallet.privateKey; - } - } - - // Add new account - addAccount(blockchain: 'solana' | 'ethereum'): WalletAccount { - if (!this.seed) { - throw new Error('Wallet not initialized'); - } - - const accountIndex = this.accounts.filter(a => a.blockchain === blockchain).length; - - if (blockchain === 'solana') { - const path = `m/44'/501'/${accountIndex}'/0'`; - const keypair = deriveSolanaKeypair(this.seed, path); - const account: WalletAccount = { - publicKey: keypair.publicKey.toString(), - blockchain: 'solana', - derivationPath: path, - }; - this.accounts.push(account); - return account; - } else { - const path = `m/44'/60'/0'/0/${accountIndex}`; - const wallet = deriveEthereumWallet(this.seed, path); - const account: WalletAccount = { - publicKey: wallet.address, - blockchain: 'ethereum', - derivationPath: path, - }; - this.accounts.push(account); - return account; - } - } - - // Clear wallet (logout) - clear(): void { - this.mnemonic = null; - this.seed = null; - this.accounts = []; - } -} diff --git a/packages/mobile-wallet/src/storage/AuthManager.ts b/packages/mobile-wallet/src/storage/AuthManager.ts deleted file mode 100644 index eedfee0..0000000 --- a/packages/mobile-wallet/src/storage/AuthManager.ts +++ /dev/null @@ -1,236 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import * as Keychain from 'react-native-keychain'; -import { Buffer } from 'buffer'; -import { randomBytes } from 'tweetnacl'; -// @ts-ignore -import Crypto from 'crypto-browserify'; - -const PIN_CONFIG_SERVICE = 'com.coralxyz.backpack.mobilewallet.pinconfig'; -const MASTER_PASSWORD_SERVICE = 'com.coralxyz.backpack.mobilewallet.masterpassword'; -const BIOMETRIC_KEYCHAIN_SERVICE = - 'com.coralxyz.backpack.mobilewallet.masterpassword.biometric'; -const BIOMETRIC_PREFERENCE_KEY = '@wallet:biometricPreference'; -const LOCK_STATE_KEY = '@wallet:pinLockState'; -const LOCK_WINDOWS_MS = [30_000, 120_000, 600_000]; -const MAX_FAILED_ATTEMPTS = 5; -const PIN_KDF_ITERATIONS = 250_000; - -interface LockState { - failedAttempts: number; - lockUntil?: number | null; -} - -interface PinConfig { - salt: string; - hash: string; - iterations: number; -} - -export class PinLockoutError extends Error { - remainingMs: number; - - constructor(remainingMs: number) { - super('PIN entry temporarily locked'); - this.remainingMs = remainingMs; - } -} - -export class AuthManager { - static async hasPin(): Promise { - const config = await Keychain.getGenericPassword({ service: PIN_CONFIG_SERVICE }); - return Boolean(config); - } - - static async isBiometricEnabled(): Promise { - const pref = await AsyncStorage.getItem(BIOMETRIC_PREFERENCE_KEY); - return pref === 'true'; - } - - static async isBiometricSupported(): Promise { - const type = await Keychain.getSupportedBiometryType(); - return Boolean(type); - } - - static async setupPin(pin: string, password: string): Promise { - AuthManager.assertValidPin(pin); - - const salt = randomBytes(16); - const derived = await AuthManager.derivePinHash(pin, salt, PIN_KDF_ITERATIONS); - const config: PinConfig = { - salt: Buffer.from(salt).toString('base64'), - hash: Buffer.from(derived).toString('base64'), - iterations: PIN_KDF_ITERATIONS, - }; - - await Keychain.setGenericPassword('pin', JSON.stringify(config), { - service: PIN_CONFIG_SERVICE, - accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY, - }); - - await AuthManager.storeMasterPassword(password); - await AuthManager.clearLockState(); - } - - static async clearSecurityState(): Promise { - await AsyncStorage.multiRemove([ - BIOMETRIC_PREFERENCE_KEY, - LOCK_STATE_KEY, - ]); - await Keychain.resetGenericPassword({ service: PIN_CONFIG_SERVICE }); - await Keychain.resetGenericPassword({ service: MASTER_PASSWORD_SERVICE }); - await Keychain.resetGenericPassword({ service: BIOMETRIC_KEYCHAIN_SERVICE }); - } - - static async unlockWithPin(pin: string): Promise { - await AuthManager.ensureNotLocked(); - const pinConfig = await AuthManager.getPinConfig(); - - try { - await AuthManager.verifyPin(pin, pinConfig); - } catch (error) { - await AuthManager.registerFailedAttempt(); - throw error; - } - - const password = await AuthManager.getMasterPassword(); - await AuthManager.clearLockState(); - return password; - } - - static async enableBiometrics(password: string): Promise { - await Keychain.setGenericPassword('wallet', password, { - service: BIOMETRIC_KEYCHAIN_SERVICE, - accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY, - accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET, - }); - await AsyncStorage.setItem(BIOMETRIC_PREFERENCE_KEY, 'true'); - } - - static async disableBiometrics(): Promise { - await Keychain.resetGenericPassword({ service: BIOMETRIC_KEYCHAIN_SERVICE }); - await AsyncStorage.setItem(BIOMETRIC_PREFERENCE_KEY, 'false'); - } - - static async unlockWithBiometrics(): Promise { - const biometricEnabled = await AuthManager.isBiometricEnabled(); - if (!biometricEnabled) { - throw new Error('Biometrics not enabled'); - } - - const credentials = await Keychain.getGenericPassword({ - service: BIOMETRIC_KEYCHAIN_SERVICE, - authenticationPrompt: { - title: 'Authenticate to unlock backpack', - description: 'Use biometrics to restore wallet access', - }, - }); - - if (!credentials) { - throw new Error('Biometric authentication was cancelled'); - } - - await AuthManager.clearLockState(); - return credentials.password; - } - - private static assertValidPin(pin: string) { - if (!/^\d{6}$/.test(pin)) { - throw new Error('PIN must be a 6-digit number'); - } - } - - private static async getLockState(): Promise { - const raw = await AsyncStorage.getItem(LOCK_STATE_KEY); - if (!raw) { - return { failedAttempts: 0, lockUntil: null }; - } - try { - return JSON.parse(raw) as LockState; - } catch { - return { failedAttempts: 0, lockUntil: null }; - } - } - - private static async saveLockState(state: LockState): Promise { - await AsyncStorage.setItem(LOCK_STATE_KEY, JSON.stringify(state)); - } - - private static async clearLockState(): Promise { - await AuthManager.saveLockState({ failedAttempts: 0, lockUntil: null }); - } - - private static async ensureNotLocked(): Promise { - const state = await AuthManager.getLockState(); - if (state.lockUntil && state.lockUntil > Date.now()) { - throw new PinLockoutError(state.lockUntil - Date.now()); - } - if (state.lockUntil && state.lockUntil <= Date.now()) { - await AuthManager.clearLockState(); - } - } - - private static async registerFailedAttempt(): Promise { - const state = await AuthManager.getLockState(); - const failedAttempts = state.failedAttempts + 1; - let lockUntil: number | null = null; - - if (failedAttempts >= MAX_FAILED_ATTEMPTS) { - const tier = Math.min( - LOCK_WINDOWS_MS.length - 1, - Math.floor((failedAttempts - MAX_FAILED_ATTEMPTS) / MAX_FAILED_ATTEMPTS) - ); - lockUntil = Date.now() + LOCK_WINDOWS_MS[tier]; - } - - await AuthManager.saveLockState({ failedAttempts, lockUntil }); - } - - private static async getPinConfig(): Promise { - const credentials = await Keychain.getGenericPassword({ service: PIN_CONFIG_SERVICE }); - if (!credentials) { - throw new Error('PIN is not configured'); - } - return JSON.parse(credentials.password) as PinConfig; - } - - private static async derivePinHash( - pin: string, - salt: Uint8Array, - iterations: number - ): Promise { - return new Promise((resolve, reject) => { - Crypto.pbkdf2(pin, Buffer.from(salt), iterations, 32, 'sha256', (err: Error, key) => - err ? reject(err) : resolve(key) - ); - }); - } - - private static async verifyPin(pin: string, config: PinConfig): Promise { - const salt = Buffer.from(config.salt, 'base64'); - const expectedHash = Buffer.from(config.hash, 'base64'); - const derived = await AuthManager.derivePinHash(pin, salt, config.iterations); - if ( - expectedHash.length !== derived.length || - !Crypto.timingSafeEqual(expectedHash, derived) - ) { - throw new Error('Invalid PIN'); - } - } - - private static async storeMasterPassword(password: string): Promise { - await Keychain.setGenericPassword('wallet', password, { - service: MASTER_PASSWORD_SERVICE, - accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY, - }); - } - - private static async getMasterPassword(): Promise { - const credentials = await Keychain.getGenericPassword({ - service: MASTER_PASSWORD_SERVICE, - }); - if (!credentials) { - throw new Error('Master password not found'); - } - return credentials.password; - } -} diff --git a/packages/mobile-wallet/src/storage/SecureStorage.ts b/packages/mobile-wallet/src/storage/SecureStorage.ts deleted file mode 100644 index d6cebe2..0000000 --- a/packages/mobile-wallet/src/storage/SecureStorage.ts +++ /dev/null @@ -1,39 +0,0 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { encrypt, decrypt, SecretPayload } from '../crypto/WalletCore'; -import { AuthManager } from './AuthManager'; - -const WALLET_KEY = '@wallet:encrypted'; -const WALLET_EXISTS_KEY = '@wallet:exists'; - -export class SecureStorage { - // Check if wallet exists - static async hasWallet(): Promise { - const exists = await AsyncStorage.getItem(WALLET_EXISTS_KEY); - return exists === 'true'; - } - - // Save encrypted wallet - static async saveWallet(mnemonic: string, password: string): Promise { - const encrypted = await encrypt(mnemonic, password); - await AsyncStorage.setItem(WALLET_KEY, JSON.stringify(encrypted)); - await AsyncStorage.setItem(WALLET_EXISTS_KEY, 'true'); - } - - // Load and decrypt wallet - static async loadWallet(password: string): Promise { - const encryptedData = await AsyncStorage.getItem(WALLET_KEY); - if (!encryptedData) { - throw new Error('No wallet found'); - } - - const encrypted: SecretPayload = JSON.parse(encryptedData); - return await decrypt(encrypted, password); - } - - // Clear wallet - static async clearWallet(): Promise { - await AsyncStorage.removeItem(WALLET_KEY); - await AsyncStorage.removeItem(WALLET_EXISTS_KEY); - await AuthManager.clearSecurityState(); - } -} diff --git a/packages/mobile-wallet/tsconfig.json b/packages/mobile-wallet/tsconfig.json deleted file mode 100644 index 7373a47..0000000 --- a/packages/mobile-wallet/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "@tsconfig/react-native/tsconfig.json", - "compilerOptions": { - "module": "ESNext", - "moduleResolution": "node", - "jsx": "react-native", - "target": "ESNext", - "lib": ["ESNext"], - "strict": false, - "esModuleInterop": true, - "skipLibCheck": true, - "resolveJsonModule": true, - "isolatedModules": true, - "allowSyntheticDefaultImports": true, - "baseUrl": ".", - "paths": { - "crypto": ["node_modules/crypto-browserify"], - "stream": ["node_modules/stream-browserify"], - "buffer": ["node_modules/buffer"] - } - }, - "include": ["src/**/*", "index.js"], - "exclude": ["node_modules"] -}