import Foundation /// Synor Wallet SDK client for Swift. /// /// Provides key management, transaction signing, and balance queries /// for the Synor blockchain. /// /// Example: /// ```swift /// let wallet = SynorWallet(config: WalletConfig(apiKey: "your-api-key")) /// /// // Create a new wallet /// let result = try await wallet.createWallet(type: .standard) /// print("Wallet ID: \(result.wallet.id)") /// print("Mnemonic: \(result.mnemonic ?? "N/A")") /// /// // Get balance /// let balance = try await wallet.getBalance(address: result.wallet.addresses[0].address) /// print("Balance: \(balance.total)") /// ``` public class SynorWallet { private let config: WalletConfig private let session: URLSession private let decoder: JSONDecoder private let encoder: JSONEncoder public init(config: WalletConfig) { self.config = config let sessionConfig = URLSessionConfiguration.default sessionConfig.timeoutIntervalForRequest = config.timeout sessionConfig.timeoutIntervalForResource = config.timeout * 2 self.session = URLSession(configuration: sessionConfig) self.decoder = JSONDecoder() self.encoder = JSONEncoder() } // MARK: - Wallet Operations /// Create a new wallet. public func createWallet(type: WalletType = .standard) async throws -> CreateWalletResult { let request = CreateWalletRequest(type: type, network: config.network) return try await post(path: "/wallets", body: request) } /// Import a wallet from a mnemonic phrase. public func importWallet(mnemonic: String, passphrase: String? = nil) async throws -> Wallet { let request = ImportWalletRequest( mnemonic: mnemonic, passphrase: passphrase, network: config.network ) return try await post(path: "/wallets/import", body: request) } /// Get a wallet by ID. public func getWallet(walletId: String) async throws -> Wallet { return try await get(path: "/wallets/\(walletId)") } /// Generate a new address for a wallet. public func generateAddress(walletId: String, isChange: Bool = false) async throws -> Address { let request = GenerateAddressRequest(isChange: isChange) return try await post(path: "/wallets/\(walletId)/addresses", body: request) } /// Get a stealth address for privacy transactions. public func getStealthAddress(walletId: String) async throws -> StealthAddress { return try await get(path: "/wallets/\(walletId)/stealth-address") } // MARK: - Signing Operations /// Sign a transaction. public func signTransaction(walletId: String, transaction: Transaction) async throws -> SignedTransaction { let request = SignTransactionRequest(walletId: walletId, transaction: transaction) return try await post(path: "/transactions/sign", body: request) } /// Sign a message with a wallet address. public func signMessage(walletId: String, message: String, addressIndex: Int = 0) async throws -> Signature { let request = SignMessageRequest( walletId: walletId, message: message, addressIndex: addressIndex ) return try await post(path: "/messages/sign", body: request) } /// Verify a message signature. public func verifyMessage(message: String, signature: String, address: String) async throws -> Bool { let request = VerifyMessageRequest(message: message, signature: signature, address: address) let response: VerifyMessageResponse = try await post(path: "/messages/verify", body: request) return response.valid } // MARK: - Balance Operations /// Get the balance for an address. public func getBalance(address: String) async throws -> Balance { return try await get(path: "/addresses/\(address)/balance") } /// Get UTXOs for an address. public func getUTXOs(address: String, minConfirmations: Int = 1) async throws -> [UTXO] { return try await get(path: "/addresses/\(address)/utxos?min_confirmations=\(minConfirmations)") } /// Estimate transaction fee. public func estimateFee(priority: Priority = .medium) async throws -> Int64 { let response: FeeEstimateResponse = try await get( path: "/fees/estimate?priority=\(priority.rawValue)" ) return response.feePerByte } // MARK: - HTTP Methods private func get(path: String) async throws -> T { return try await executeWithRetry { try await self.performRequest(method: "GET", path: path, body: nil as Empty?) } } private func post(path: String, body: R) async throws -> T { return try await executeWithRetry { try await self.performRequest(method: "POST", path: path, body: body) } } private func performRequest( method: String, path: String, body: R? ) async throws -> T { guard let url = URL(string: "\(config.endpoint)\(path)") else { throw WalletError.invalidResponse } var request = URLRequest(url: url) request.httpMethod = method request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Content-Type") if let body = body { do { request.httpBody = try encoder.encode(body) } catch { throw WalletError.encodingError(error) } } let (data, response): (Data, URLResponse) do { (data, response) = try await session.data(for: request) } catch { throw WalletError.networkError(error) } guard let httpResponse = response as? HTTPURLResponse else { throw WalletError.invalidResponse } if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 { do { return try decoder.decode(T.self, from: data) } catch { throw WalletError.decodingError(error) } } let message = String(data: data, encoding: .utf8) ?? "Unknown error" throw WalletError.httpError(statusCode: httpResponse.statusCode, message: message) } private func executeWithRetry(_ operation: () async throws -> T) async throws -> T { var lastError: Error? for attempt in 0..