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).
279 lines
10 KiB
Swift
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
|
|
}
|
|
}
|