synor/sdk/swift/Sources/Synor/Mining/SynorMining.swift
Gulshan Yadav 6607223c9e feat(sdk): complete Phase 4 SDKs for all remaining languages
Add Economics, Governance, and Mining SDKs for:
- Java: Full SDK with CompletableFuture async operations
- Kotlin: Coroutine-based SDK with suspend functions
- Swift: Modern Swift SDK with async/await
- Flutter/Dart: Complete Dart SDK with Future-based API
- C: Header files and implementations with opaque handles
- C++: Modern C++17 with std::future and PIMPL pattern
- C#: Records, async/await Tasks, and IDisposable
- Ruby: Struct-based types with Faraday HTTP client

Also includes minor Dart lint fixes (const exceptions).
2026-01-28 08:33:20 +05:30

279 lines
10 KiB
Swift

import Foundation
/// Synor Mining SDK client for Swift.
///
/// Provides pool connections, block templates, hashrate stats, and GPU management.
public class SynorMining {
private let config: MiningConfig
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
private var closed = false
private var activeConnection: StratumConnection?
public init(config: MiningConfig) {
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: - Pool Operations
/// Connect to a mining pool.
public func connect(pool: PoolConfig) async throws -> StratumConnection {
var body: [String: Any] = ["url": pool.url, "user": pool.user]
if let password = pool.password { body["password"] = password }
if let algorithm = pool.algorithm { body["algorithm"] = algorithm }
if let difficulty = pool.difficulty { body["difficulty"] = difficulty }
let conn: StratumConnection = try await post(path: "/pool/connect", body: body)
activeConnection = conn
return conn
}
/// Disconnect from the pool.
public func disconnect() async throws {
guard activeConnection != nil else { return }
let _: [String: Any] = try await post(path: "/pool/disconnect", body: [:] as [String: String])
activeConnection = nil
}
/// Get current connection.
public func getConnection() async throws -> StratumConnection {
let conn: StratumConnection = try await get(path: "/pool/connection")
activeConnection = conn
return conn
}
/// Get pool stats.
public func getPoolStats() async throws -> PoolStats {
return try await get(path: "/pool/stats")
}
/// Check if connected.
public var isConnected: Bool {
activeConnection?.status == .connected
}
// MARK: - Mining Operations
/// Get block template.
public func getBlockTemplate() async throws -> BlockTemplate {
return try await get(path: "/mining/template")
}
/// Submit mined work.
public func submitWork(_ work: MinedWork) async throws -> SubmitResult {
let body: [String: Any] = [
"templateId": work.templateId,
"nonce": work.nonce,
"extraNonce": work.extraNonce,
"timestamp": work.timestamp,
"hash": work.hash
]
return try await post(path: "/mining/submit", body: body)
}
/// Start mining.
public func startMining(algorithm: String? = nil) async throws {
var body: [String: Any] = [:]
if let algorithm = algorithm { body["algorithm"] = algorithm }
let _: [String: Any] = try await post(path: "/mining/start", body: body)
}
/// Stop mining.
public func stopMining() async throws {
let _: [String: Any] = try await post(path: "/mining/stop", body: [:] as [String: String])
}
/// Check if mining.
public func isMining() async throws -> Bool {
let response: [String: Bool] = try await get(path: "/mining/status")
return response["mining"] ?? false
}
// MARK: - Stats Operations
/// Get hashrate.
public func getHashrate() async throws -> Hashrate {
return try await get(path: "/stats/hashrate")
}
/// Get mining stats.
public func getStats() async throws -> MiningStats {
return try await get(path: "/stats")
}
/// Get earnings.
public func getEarnings(period: TimePeriod? = nil) async throws -> Earnings {
let path = period != nil ? "/stats/earnings?period=\(period!.rawValue)" : "/stats/earnings"
return try await get(path: path)
}
/// Get share stats.
public func getShareStats() async throws -> ShareStats {
return try await get(path: "/stats/shares")
}
// MARK: - Device Operations
/// List devices.
public func listDevices() async throws -> [MiningDevice] {
let response: DevicesResponse = try await get(path: "/devices")
return response.devices ?? []
}
/// Get device.
public func getDevice(deviceId: String) async throws -> MiningDevice {
return try await get(path: "/devices/\(deviceId.urlEncoded)")
}
/// Set device config.
public func setDeviceConfig(deviceId: String, config: DeviceConfig) async throws -> MiningDevice {
var body: [String: Any] = ["enabled": config.enabled]
if let intensity = config.intensity { body["intensity"] = intensity }
if let powerLimit = config.powerLimit { body["powerLimit"] = powerLimit }
if let coreClockOffset = config.coreClockOffset { body["coreClockOffset"] = coreClockOffset }
if let memoryClockOffset = config.memoryClockOffset { body["memoryClockOffset"] = memoryClockOffset }
if let fanSpeed = config.fanSpeed { body["fanSpeed"] = fanSpeed }
return try await put(path: "/devices/\(deviceId.urlEncoded)/config", body: body)
}
/// Enable device.
public func enableDevice(deviceId: String) async throws {
_ = try await setDeviceConfig(deviceId: deviceId, config: DeviceConfig(enabled: true))
}
/// Disable device.
public func disableDevice(deviceId: String) async throws {
_ = try await setDeviceConfig(deviceId: deviceId, config: DeviceConfig(enabled: false))
}
// MARK: - Worker Operations
/// List workers.
public func listWorkers() async throws -> [WorkerInfo] {
let response: WorkersResponse = try await get(path: "/workers")
return response.workers ?? []
}
/// Get worker.
public func getWorker(workerId: String) async throws -> WorkerInfo {
return try await get(path: "/workers/\(workerId.urlEncoded)")
}
/// Remove worker.
public func removeWorker(workerId: String) async throws {
let _: [String: Any] = try await delete(path: "/workers/\(workerId.urlEncoded)")
}
// MARK: - Algorithm Operations
/// List algorithms.
public func listAlgorithms() async throws -> [MiningAlgorithm] {
let response: AlgorithmsResponse = try await get(path: "/algorithms")
return response.algorithms ?? []
}
/// Get algorithm.
public func getAlgorithm(name: String) async throws -> MiningAlgorithm {
return try await get(path: "/algorithms/\(name.urlEncoded)")
}
/// Switch algorithm.
public func switchAlgorithm(_ algorithm: String) async throws {
let _: [String: Any] = try await post(path: "/algorithms/switch", body: ["algorithm": algorithm])
}
/// Get most profitable algorithm.
public func getMostProfitable() async throws -> MiningAlgorithm {
return try await get(path: "/algorithms/profitable")
}
// MARK: - Lifecycle
public func close() {
closed = true
activeConnection = nil
}
public var isClosed: Bool { closed }
public func healthCheck() async -> Bool {
do {
let response: [String: String] = try await get(path: "/health")
return response["status"] == "healthy"
} catch { return false }
}
// MARK: - Private Methods
private func get<T: Decodable>(path: String) async throws -> T {
return try await request(method: "GET", path: path)
}
private func post<T: Decodable>(path: String, body: Any) async throws -> T {
return try await request(method: "POST", path: path, body: body)
}
private func put<T: Decodable>(path: String, body: Any) async throws -> T {
return try await request(method: "PUT", path: path, body: body)
}
private func delete<T: Decodable>(path: String) async throws -> T {
return try await request(method: "DELETE", path: path)
}
private func request<T: Decodable>(method: String, path: String, body: Any? = nil) async throws -> T {
guard !closed else { throw MiningError(message: "Client has been closed") }
var lastError: Error?
for attempt in 0..<config.retries {
do {
return try await doRequest(method: method, path: path, body: body)
} catch {
lastError = error
if attempt < config.retries - 1 {
try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempt))) * 1_000_000_000)
}
}
}
throw lastError ?? MiningError(message: "Unknown error")
}
private func doRequest<T: Decodable>(method: String, path: String, body: Any?) async throws -> T {
guard let url = URL(string: config.endpoint + path) else { throw MiningError(message: "Invalid URL") }
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
if let body = body { request.httpBody = try JSONSerialization.data(withJSONObject: body) }
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else { throw MiningError(message: "Invalid response") }
if httpResponse.statusCode >= 400 {
let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
throw MiningError(
message: errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)",
code: errorInfo?["code"] as? String,
statusCode: httpResponse.statusCode
)
}
return try decoder.decode(T.self, from: data)
}
}
extension String {
fileprivate var urlEncoded: String {
addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
}
}