synor/sdk/swift/Sources/Synor/Economics/SynorEconomics.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

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
}
}