Skip to main content
Glama
StevenGeller

LDK MCP Server

by StevenGeller

ios_biometric_auth

Enable biometric authentication for Lightning Network operations on iOS devices. Use Touch ID or Face ID to authorize payments, channel management, and seed export.

Instructions

Integrate Touch/Face ID with Lightning operations

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
operationNoOperation requiring biometric authsend_payment
requireAuthNoWhether to require biometric auth

Implementation Reference

  • The main handler (execute function) for the ios_biometric_auth tool. It processes input arguments, calls the iosService.testBiometricAuth() helper, and returns a ToolResult with success status, security guidelines, and comprehensive SwiftUI example code for biometric-protected payments.
      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
          };
        }
      }
  • The inputSchema defining the parameters for the ios_biometric_auth tool: operation (enum: send_payment, etc.) and requireAuth (boolean).
    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:38-62 (registration)
    Registration of the biometricAuthTool (imported line 21, included line 47) in the central tools array used by MCP server request handlers for listTools and callTool.
    const tools = [
      generateInvoiceTool,
      payInvoiceTool,
      getChannelStatusTool,
      getNodeInfoTool,
      backupStateTool,
      keychainTestTool,
      backgroundTestTool,
      pushNotificationTool,
      biometricAuthTool,
      createChannelTool,
      closeChannelTool,
      getBalanceTool,
      decodeInvoiceTool,
      listPaymentsTool,
      estimateFeeTool,
      generateMnemonicTool,
      deriveAddressTool,
      getSwiftCodeTool,
      getArchitectureTool,
      testScenarioTool,
      networkGraphTool,
      eventHandlingTool,
      chainSyncTool,
    ];
  • Supporting helper method testBiometricAuth() in IOSService class, which returns mock success response with detailed Swift code examples for biometric authentication using LocalAuthentication and biometric-protected Keychain access.
      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
        };
      }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/StevenGeller/ldk-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server