ios_biometric_auth
Enable biometric authentication (Touch/Face ID) for secure Lightning Network operations like sending payments, opening/closing channels, and exporting seeds. Integrates with LDK MCP Server for iOS Lightning wallet development.
Instructions
Integrate Touch/Face ID with Lightning operations
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| operation | No | Operation requiring biometric auth | send_payment |
| requireAuth | No | Whether to require biometric auth |
Input Schema (JSON Schema)
{
"properties": {
"operation": {
"default": "send_payment",
"description": "Operation requiring biometric auth",
"enum": [
"send_payment",
"open_channel",
"close_channel",
"export_seed"
],
"type": "string"
},
"requireAuth": {
"default": true,
"description": "Whether to require biometric auth",
"type": "boolean"
}
},
"type": "object"
}
Implementation Reference
- src/tools/iosBiometricAuth.ts:25-181 (handler)The execute function implementing the core logic of the 'ios_biometric_auth' tool. It calls IOSService.testBiometricAuth() to get biometric auth details and returns a formatted response with security guidelines and SwiftUI integration example.execute: async (args: any): Promise<ToolResult> => { try { const result = await iosService.testBiometricAuth(); return { content: [{ type: 'text', text: JSON.stringify({ success: result.success, message: result.message, swiftExample: result.swiftCode, operation: args.operation, requireAuth: args.requireAuth, securityGuidelines: [ 'Always require biometric auth for seed access', 'Use biometrics for high-value payments', 'Provide passcode fallback option', 'Clear biometric data on app reinstall', 'Implement anti-tampering measures', 'Use LAContext evaluation for each operation' ], uiIntegration: ` // SwiftUI biometric-protected payment view import SwiftUI import LocalAuthentication struct SecurePaymentView: View { @State private var isAuthenticated = false @State private var showingError = false @State private var errorMessage = "" let invoice: String let amountSats: Int var body: some View { VStack(spacing: 30) { // Payment details VStack(spacing: 16) { Image(systemName: "bolt.circle.fill") .font(.system(size: 60)) .foregroundColor(.orange) Text("Confirm Payment") .font(.title) .fontWeight(.semibold) Text("\\(amountSats) sats") .font(.title2) .foregroundColor(.secondary) } // Biometric prompt if !isAuthenticated { VStack(spacing: 20) { Image(systemName: biometricIcon) .font(.system(size: 50)) .foregroundColor(.blue) Text("Authenticate to send payment") .font(.headline) Button(action: authenticate) { Label("Authenticate", systemImage: biometricIcon) .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) } } else { // Payment in progress VStack(spacing: 16) { ProgressView() .scaleEffect(1.5) Text("Sending payment...") .font(.headline) } .onAppear { sendPayment() } } } .padding() .alert("Authentication Failed", isPresented: $showingError) { Button("OK") { } } message: { Text(errorMessage) } } var biometricIcon: String { let biometricType = BiometricLightningAuth.getBiometricType() switch biometricType { case .faceID: return "faceid" case .touchID: return "touchid" default: return "lock" } } func authenticate() { Task { let authenticated = await BiometricLightningAuth.authenticateForPayment( amount: UInt64(amountSats) ) if authenticated { withAnimation { isAuthenticated = true } } else { errorMessage = "Authentication failed. Please try again." showingError = true } } } func sendPayment() { Task { do { // Send payment via LDK try await LDKManager.shared.payInvoice( invoice: invoice, maxFeeSats: UInt64(amountSats) / 100 // 1% max fee ) // Navigate to success view await MainActor.run { // Show success } } catch { await MainActor.run { errorMessage = "Payment failed: \\(error.localizedDescription)" showingError = true } } } } }`.trim() }, null, 2) }] }; } catch (error) { return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : 'Unknown error' }, null, 2) }], isError: true }; } }
- src/tools/iosBiometricAuth.ts:9-24 (schema)Input schema defining the parameters accepted by the ios_biometric_auth tool.inputSchema: { type: 'object', properties: { operation: { type: 'string', enum: ['send_payment', 'open_channel', 'close_channel', 'export_seed'], description: 'Operation requiring biometric auth', default: 'send_payment' }, requireAuth: { type: 'boolean', description: 'Whether to require biometric auth', default: true } } },
- src/index.ts:21-21 (registration)Import statement registering the biometricAuthTool for use in the MCP server.import { biometricAuthTool } from './tools/iosBiometricAuth.js';
- src/index.ts:47-47 (registration)Addition of biometricAuthTool to the main tools array used by the MCP server.biometricAuthTool,
- src/services/iosService.ts:207-317 (helper)Helper method in IOSService that generates comprehensive Swift code examples for iOS biometric authentication, used by the tool handler.// Simulate biometric authentication async testBiometricAuth(): Promise<{ success: boolean; message: string; swiftCode: string; }> { const swiftCode = ` import LocalAuthentication import Security class BiometricLightningAuth { enum BiometricType { case none case touchID case faceID } static func getBiometricType() -> BiometricType { let context = LAContext() var error: NSError? guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else { return .none } switch context.biometryType { case .touchID: return .touchID case .faceID: return .faceID default: return .none } } static func authenticateForPayment(amount: UInt64) async -> Bool { let context = LAContext() context.localizedCancelTitle = "Cancel" let reason = "Authenticate to send \\(amount / 1000) sats" do { let success = try await context.evaluatePolicy( .deviceOwnerAuthenticationWithBiometrics, localizedReason: reason ) return success } catch { print("Biometric authentication failed: \\(error)") return false } } static func protectSeedWithBiometrics(seed: Data) throws -> Data { let context = LAContext() context.localizedReason = "Protect your Lightning wallet" let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: "ldk_protected_seed", kSecValueData as String: seed, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAttrAccessControl as String: SecAccessControlCreateWithFlags( nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .biometryCurrentSet, nil )! ] // Remove existing item SecItemDelete(query as CFDictionary) // Add with biometric protection let status = SecItemAdd(query as CFDictionary, nil) guard status == errSecSuccess else { throw NSError(domain: "BiometricAuth", code: Int(status)) } return seed } static func retrieveSeedWithBiometrics() async throws -> Data { let context = LAContext() context.localizedReason = "Access your Lightning wallet" let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: "ldk_protected_seed", kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne, kSecUseAuthenticationContext as String: context ] var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) guard status == errSecSuccess, let data = result as? Data else { throw NSError(domain: "BiometricAuth", code: Int(status)) } return data } }`.trim(); return { success: true, message: 'Biometric authentication test completed', swiftCode }; }