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).
286 lines
10 KiB
Swift
286 lines
10 KiB
Swift
import Foundation
|
|
|
|
/// Synor Economics SDK client for Swift.
|
|
///
|
|
/// Provides pricing, billing, staking, and discount operations.
|
|
///
|
|
/// Example:
|
|
/// ```swift
|
|
/// let economics = SynorEconomics(config: EconomicsConfig(apiKey: "your-api-key"))
|
|
///
|
|
/// // Get price for compute usage
|
|
/// let usage = UsageMetrics(computeHours: 100, gpuHours: 10)
|
|
/// let price = try await economics.getPrice(service: .compute, usage: usage)
|
|
///
|
|
/// // Stake tokens
|
|
/// let receipt = try await economics.stake(amount: "1000000000000000000")
|
|
/// ```
|
|
public class SynorEconomics {
|
|
private let config: EconomicsConfig
|
|
private let session: URLSession
|
|
private let decoder: JSONDecoder
|
|
private let encoder: JSONEncoder
|
|
private var closed = false
|
|
|
|
public init(config: EconomicsConfig) {
|
|
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: - Pricing Operations
|
|
|
|
/// Get price for service usage.
|
|
public func getPrice(service: ServiceType, usage: UsageMetrics) async throws -> Price {
|
|
let body: [String: Any] = [
|
|
"service": service.rawValue,
|
|
"usage": [
|
|
"computeHours": usage.computeHours,
|
|
"storageGb": usage.storageGb,
|
|
"requests": usage.requests,
|
|
"bandwidthGb": usage.bandwidthGb,
|
|
"gpuHours": usage.gpuHours
|
|
]
|
|
]
|
|
return try await post(path: "/pricing/calculate", body: body)
|
|
}
|
|
|
|
/// Estimate cost for a usage plan.
|
|
public func estimateCost(plan: UsagePlan) async throws -> CostEstimate {
|
|
let body: [String: Any] = [
|
|
"service": plan.service.rawValue,
|
|
"estimatedUsage": [
|
|
"computeHours": plan.estimatedUsage.computeHours,
|
|
"storageGb": plan.estimatedUsage.storageGb,
|
|
"requests": plan.estimatedUsage.requests,
|
|
"bandwidthGb": plan.estimatedUsage.bandwidthGb,
|
|
"gpuHours": plan.estimatedUsage.gpuHours
|
|
],
|
|
"period": plan.period.rawValue
|
|
]
|
|
return try await post(path: "/pricing/estimate", body: body)
|
|
}
|
|
|
|
/// Get pricing tiers for a service.
|
|
public func getPricingTiers(service: ServiceType) async throws -> [PricingTier] {
|
|
let response: TiersResponse = try await get(path: "/pricing/tiers/\(service.rawValue)")
|
|
return response.tiers ?? []
|
|
}
|
|
|
|
// MARK: - Billing Operations
|
|
|
|
/// Get usage for a billing period.
|
|
public func getUsage(period: BillingPeriod? = nil) async throws -> Usage {
|
|
var path = "/billing/usage"
|
|
if let period = period {
|
|
path += "?period=\(period.rawValue)"
|
|
}
|
|
return try await get(path: path)
|
|
}
|
|
|
|
/// Get usage by date range.
|
|
public func getUsageByDateRange(startDate: Int64, endDate: Int64) async throws -> Usage {
|
|
return try await get(path: "/billing/usage?startDate=\(startDate)&endDate=\(endDate)")
|
|
}
|
|
|
|
/// Get all invoices.
|
|
public func getInvoices() async throws -> [Invoice] {
|
|
let response: InvoicesResponse = try await get(path: "/billing/invoices")
|
|
return response.invoices ?? []
|
|
}
|
|
|
|
/// Get invoice by ID.
|
|
public func getInvoice(invoiceId: String) async throws -> Invoice {
|
|
return try await get(path: "/billing/invoices/\(invoiceId.urlEncoded)")
|
|
}
|
|
|
|
/// Download invoice as PDF data.
|
|
public func downloadInvoice(invoiceId: String) async throws -> Data {
|
|
let response: [String: String] = try await get(path: "/billing/invoices/\(invoiceId.urlEncoded)/pdf")
|
|
guard let base64 = response["data"],
|
|
let data = Data(base64Encoded: base64) else {
|
|
throw EconomicsError(message: "Invalid PDF data")
|
|
}
|
|
return data
|
|
}
|
|
|
|
/// Get account balance.
|
|
public func getBalance() async throws -> AccountBalance {
|
|
return try await get(path: "/billing/balance")
|
|
}
|
|
|
|
/// Add credits to account.
|
|
public func addCredits(amount: String, paymentMethod: String) async throws {
|
|
let body = ["amount": amount, "paymentMethod": paymentMethod]
|
|
let _: [String: Any] = try await post(path: "/billing/credits", body: body)
|
|
}
|
|
|
|
// MARK: - Staking Operations
|
|
|
|
/// Stake tokens.
|
|
public func stake(amount: String, durationDays: Int? = nil) async throws -> StakeReceipt {
|
|
var body: [String: Any] = ["amount": amount]
|
|
if let duration = durationDays { body["durationDays"] = duration }
|
|
return try await post(path: "/staking/stake", body: body)
|
|
}
|
|
|
|
/// Unstake tokens.
|
|
public func unstake(stakeId: String) async throws -> UnstakeReceipt {
|
|
return try await post(path: "/staking/unstake/\(stakeId.urlEncoded)", body: [:] as [String: String])
|
|
}
|
|
|
|
/// Get all stakes.
|
|
public func getStakes() async throws -> [StakeInfo] {
|
|
let response: StakesResponse = try await get(path: "/staking/stakes")
|
|
return response.stakes ?? []
|
|
}
|
|
|
|
/// Get stake by ID.
|
|
public func getStake(stakeId: String) async throws -> StakeInfo {
|
|
return try await get(path: "/staking/stakes/\(stakeId.urlEncoded)")
|
|
}
|
|
|
|
/// Get staking rewards.
|
|
public func getStakingRewards() async throws -> Rewards {
|
|
return try await get(path: "/staking/rewards")
|
|
}
|
|
|
|
/// Claim staking rewards.
|
|
public func claimRewards() async throws -> String {
|
|
let response: [String: String] = try await post(path: "/staking/rewards/claim", body: [:] as [String: String])
|
|
return response["txHash"] ?? ""
|
|
}
|
|
|
|
/// Get current APY.
|
|
public func getCurrentApy() async throws -> Double {
|
|
let response: [String: Double] = try await get(path: "/staking/apy")
|
|
return response["apy"] ?? 0
|
|
}
|
|
|
|
// MARK: - Discount Operations
|
|
|
|
/// Apply a discount code.
|
|
public func applyDiscount(code: String) async throws -> AppliedDiscount {
|
|
return try await post(path: "/discounts/apply", body: ["code": code])
|
|
}
|
|
|
|
/// Get available discounts.
|
|
public func getAvailableDiscounts() async throws -> [Discount] {
|
|
let response: DiscountsResponse = try await get(path: "/discounts/available")
|
|
return response.discounts ?? []
|
|
}
|
|
|
|
/// Validate a discount code.
|
|
public func validateDiscount(code: String) async throws -> Discount {
|
|
return try await get(path: "/discounts/validate/\(code.urlEncoded)")
|
|
}
|
|
|
|
/// Get applied discounts.
|
|
public func getAppliedDiscounts() async throws -> [AppliedDiscount] {
|
|
let response: AppliedDiscountsResponse = try await get(path: "/discounts/applied")
|
|
return response.discounts ?? []
|
|
}
|
|
|
|
/// Remove a discount.
|
|
public func removeDiscount(discountId: String) async throws {
|
|
let _: [String: Any] = try await delete(path: "/discounts/\(discountId.urlEncoded)")
|
|
}
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
/// Close the client.
|
|
public func close() {
|
|
closed = true
|
|
}
|
|
|
|
/// Check if client is closed.
|
|
public var isClosed: Bool { closed }
|
|
|
|
/// Health check.
|
|
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 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 EconomicsError(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 ?? EconomicsError(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 EconomicsError(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 EconomicsError(message: "Invalid response")
|
|
}
|
|
|
|
if httpResponse.statusCode >= 400 {
|
|
let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
throw EconomicsError(
|
|
message: errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)",
|
|
code: errorInfo?["code"] as? String,
|
|
statusCode: httpResponse.statusCode
|
|
)
|
|
}
|
|
|
|
return try decoder.decode(T.self, from: data)
|
|
}
|
|
}
|
|
|
|
// MARK: - Extensions
|
|
|
|
extension String {
|
|
var urlEncoded: String {
|
|
addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
|
|
}
|
|
}
|