// // AddressValidator.swift // OS Vault // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // import Foundation import Web3Core /// Recipient address validation: hex shape + EIP-55 checksum. /// All-lowercase (or all-uppercase) addresses carry no checksum information — /// they are accepted with a warning, per the project security rules. public enum AddressValidator { public enum Verdict: Equatable { /// Mixed-case address whose EIP-55 checksum matches. case valid(checksummed: String) /// Well-formed but caseless — no checksum to verify. Warn, don't block. case validNoChecksum(checksummed: String) case invalid } public static func validate(_ input: String) -> Verdict { let candidate = input.trimmingCharacters(in: .whitespacesAndNewlines) guard candidate.count == 42, candidate.hasPrefix("0x") else { return .invalid } let body = String(candidate.dropFirst(2)) guard body.allSatisfy({ $0.isHexDigit }) else { return .invalid } guard let checksummed = EthereumAddress.toChecksumAddress(candidate) else { return .invalid } // An exact checksum match is valid even when it happens to be all one // case (some EIP-55 checksums are, e.g. 0x529084…9EE7). if candidate == checksummed { return .valid(checksummed: checksummed) } let letters = body.filter { $0.isLetter } let caseless = letters.isEmpty || letters.allSatisfy { $0.isLowercase } || letters.allSatisfy { $0.isUppercase } return caseless ? .validNoChecksum(checksummed: checksummed) : .invalid } }