import Foundation /// Synor ZK SDK for Swift /// /// Zero-Knowledge proof systems for ZK-Rollups and privacy. public class SynorZk { private let config: ZkConfig private let session: URLSession private var _closed = false public lazy var proofs = ProofsClient(zk: self) public lazy var circuits = CircuitsClient(zk: self) public lazy var rollup = RollupClient(zk: self) public lazy var state = StateClient(zk: self) public lazy var ceremony = CeremonyClient(zk: self) public init(config: ZkConfig) { self.config = config let sessionConfig = URLSessionConfiguration.default sessionConfig.timeoutIntervalForRequest = TimeInterval(config.timeout) self.session = URLSession(configuration: sessionConfig) } public var defaultProofSystem: ProofSystem { config.defaultProofSystem } public func healthCheck() async -> Bool { do { let result = try await get("/health") return result["status"] as? String == "healthy" } catch { return false } } public func getInfo() async throws -> [String: Any] { try await get("/info") } public func close() { _closed = true session.invalidateAndCancel() } public var isClosed: Bool { _closed } // Internal HTTP methods func get(_ path: String) async throws -> [String: Any] { try checkClosed() var request = URLRequest(url: URL(string: "\(config.endpoint)\(path)")!) request.httpMethod = "GET" addHeaders(&request) return try await performRequest(request) } func post(_ path: String, body: [String: Any]) async throws -> [String: Any] { try checkClosed() var request = URLRequest(url: URL(string: "\(config.endpoint)\(path)")!) request.httpMethod = "POST" request.httpBody = try JSONSerialization.data(withJSONObject: body) addHeaders(&request) return try await performRequest(request) } private func addHeaders(_ request: inout URLRequest) { request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization") request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version") } private func performRequest(_ request: URLRequest) async throws -> [String: Any] { let (data, response) = try await session.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { throw ZkError("Invalid response") } if httpResponse.statusCode >= 400 { let error = try? JSONSerialization.jsonObject(with: data) as? [String: Any] throw ZkError( error?["message"] as? String ?? "HTTP \(httpResponse.statusCode)", code: error?["code"] as? String, status: httpResponse.statusCode ) } guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { throw ZkError("Invalid JSON response") } return json } private func checkClosed() throws { if _closed { throw ZkError("Client has been closed", code: "CLIENT_CLOSED") } } /// Proofs sub-client public class ProofsClient { private let zk: SynorZk init(zk: SynorZk) { self.zk = zk } public func generate(_ request: ProofRequest) async throws -> Proof { var body: [String: Any] = [ "circuit_type": request.circuitType.rawValue, "public_inputs": request.publicInputs, "private_inputs": request.privateInputs, "system": (request.system ?? zk.defaultProofSystem).rawValue ] let result = try await zk.post("/proofs/generate", body: body) return try Proof.fromJson(result) } public func verify(_ proof: Proof) async throws -> VerificationResult { let body: [String: Any] = [ "proof_id": proof.id, "data": proof.data, "public_inputs": proof.publicInputs, "system": proof.system.rawValue, "circuit_type": proof.circuitType.rawValue ] let result = try await zk.post("/proofs/verify", body: body) return VerificationResult.fromJson(result) } public func get(proofId: String) async throws -> Proof { let result = try await zk.get("/proofs/\(proofId)") return try Proof.fromJson(result) } public func list(limit: Int? = nil, offset: Int? = nil) async throws -> [Proof] { var path = "/proofs" var params: [String] = [] if let limit = limit { params.append("limit=\(limit)") } if let offset = offset { params.append("offset=\(offset)") } if !params.isEmpty { path += "?\(params.joined(separator: "&"))" } let result = try await zk.get(path) guard let proofs = result["proofs"] as? [[String: Any]] else { return [] } return try proofs.map { try Proof.fromJson($0) } } public func serialize(_ proof: Proof) async throws -> Data { let result = try await zk.post("/proofs/serialize", body: ["proof_id": proof.id]) guard let dataStr = result["data"] as? String, let data = Data(base64Encoded: dataStr) else { throw ZkError("Invalid response") } return data } public func deserialize(_ data: Data, system: ProofSystem) async throws -> Proof { let result = try await zk.post("/proofs/deserialize", body: [ "data": data.base64EncodedString(), "system": system.rawValue ]) return try Proof.fromJson(result) } } /// Circuits sub-client public class CircuitsClient { private let zk: SynorZk init(zk: SynorZk) { self.zk = zk } public func getVerificationKey(circuitType: CircuitType, system: ProofSystem? = nil) async throws -> VerificationKey { let sys = system ?? zk.defaultProofSystem let result = try await zk.get("/circuits/\(circuitType.rawValue)/vk?system=\(sys.rawValue)") return try VerificationKey.fromJson(result) } public func getProvingKey(circuitType: CircuitType, system: ProofSystem? = nil) async throws -> ProvingKey { let sys = system ?? zk.defaultProofSystem let result = try await zk.get("/circuits/\(circuitType.rawValue)/pk?system=\(sys.rawValue)") return try ProvingKey.fromJson(result) } public func list() async throws -> [CircuitConfig] { let result = try await zk.get("/circuits") guard let circuits = result["circuits"] as? [[String: Any]] else { return [] } return circuits.compactMap { CircuitConfig.fromJson($0) } } public func getConfig(circuitType: CircuitType) async throws -> CircuitConfig { let result = try await zk.get("/circuits/\(circuitType.rawValue)/config") guard let config = CircuitConfig.fromJson(result) else { throw ZkError("Invalid response") } return config } public func compile(_ config: CircuitConfig) async throws -> String { let result = try await zk.post("/circuits/compile", body: config.toJson()) guard let circuitId = result["circuit_id"] as? String else { throw ZkError("Invalid response") } return circuitId } } /// Rollup sub-client public class RollupClient { private let zk: SynorZk init(zk: SynorZk) { self.zk = zk } public func getStats() async throws -> RollupStats { let result = try await zk.get("/rollup/stats") return RollupStats.fromJson(result) } public func getCurrentBatch() async throws -> Batch { let result = try await zk.get("/rollup/batch/current") return try Batch.fromJson(result) } public func getBatch(_ batchNumber: Int) async throws -> Batch { let result = try await zk.get("/rollup/batch/\(batchNumber)") return try Batch.fromJson(result) } public func submitTransfer(_ transfer: Transfer) async throws -> String { let result = try await zk.post("/rollup/transfer", body: transfer.toJson()) guard let txId = result["tx_id"] as? String else { throw ZkError("Invalid response") } return txId } public func submitDeposit(_ deposit: Deposit) async throws -> String { let result = try await zk.post("/rollup/deposit", body: deposit.toJson()) guard let txId = result["tx_id"] as? String else { throw ZkError("Invalid response") } return txId } public func submitWithdrawal(_ withdrawal: Withdrawal) async throws -> String { let result = try await zk.post("/rollup/withdraw", body: withdrawal.toJson()) guard let txId = result["tx_id"] as? String else { throw ZkError("Invalid response") } return txId } public func finalizeBatch() async throws -> Batch { let result = try await zk.post("/rollup/batch/finalize", body: [:]) return try Batch.fromJson(result) } public func getPendingTransactions() async throws -> [Transfer] { let result = try await zk.get("/rollup/pending") guard let transactions = result["transactions"] as? [[String: Any]] else { return [] } return transactions.compactMap { Transfer.fromJson($0) } } } /// State sub-client public class StateClient { private let zk: SynorZk init(zk: SynorZk) { self.zk = zk } public func getRoot() async throws -> String { let result = try await zk.get("/state/root") guard let root = result["root"] as? String else { throw ZkError("Invalid response") } return root } public func getAccount(address: String) async throws -> AccountState { let result = try await zk.get("/state/account/\(address)") return AccountState.fromJson(result) } public func getMerkleProof(address: String) async throws -> MerkleProof { let result = try await zk.get("/state/proof/\(address)") return MerkleProof.fromJson(result) } public func verifyMerkleProof(_ proof: MerkleProof) async throws -> Bool { let result = try await zk.post("/state/proof/verify", body: proof.toJson()) return result["valid"] as? Bool ?? false } public func getStateAtBatch(_ batchNumber: Int) async throws -> [String: Any] { try await zk.get("/state/batch/\(batchNumber)") } } /// Ceremony sub-client public class CeremonyClient { private let zk: SynorZk init(zk: SynorZk) { self.zk = zk } public func getStatus(circuitType: CircuitType, system: ProofSystem? = nil) async throws -> TrustedSetup { let sys = system ?? zk.defaultProofSystem let result = try await zk.get("/ceremony/\(circuitType.rawValue)?system=\(sys.rawValue)") return TrustedSetup.fromJson(result) } public func contribute(circuitType: CircuitType, entropy: Data, system: ProofSystem? = nil) async throws -> CeremonyContribution { let sys = system ?? zk.defaultProofSystem let result = try await zk.post("/ceremony/contribute", body: [ "circuit_type": circuitType.rawValue, "entropy": entropy.base64EncodedString(), "system": sys.rawValue ]) return CeremonyContribution.fromJson(result) } public func verifyContribution(circuitType: CircuitType, contributionHash: String) async throws -> Bool { let result = try await zk.post("/ceremony/verify", body: [ "circuit_type": circuitType.rawValue, "contribution_hash": contributionHash ]) return result["valid"] as? Bool ?? false } public func listContributions(circuitType: CircuitType) async throws -> [CeremonyContribution] { let result = try await zk.get("/ceremony/\(circuitType.rawValue)/contributions") guard let contributions = result["contributions"] as? [[String: Any]] else { return [] } return contributions.map { CeremonyContribution.fromJson($0) } } } }