synor/sdk/swift/Sources/SynorDex/SynorDex.swift
Gulshan Yadav e7dc8f70a0 feat(sdk): implement DEX SDK with perpetual futures for all 12 languages
Complete decentralized exchange client implementation featuring:
- AMM swaps (constant product, stable, concentrated liquidity)
- Liquidity provision with impermanent loss tracking
- Perpetual futures (up to 100x leverage, funding rates, liquidation)
- Order books with limit orders (GTC, IOC, FOK, GTD)
- Yield farming & staking with reward claiming
- Real-time WebSocket subscriptions
- Analytics (OHLCV, trade history, volume, TVL)

Languages: JS/TS, Python, Go, Rust, Java, Kotlin, Swift, Flutter,
C, C++, C#, Ruby
2026-01-28 12:32:04 +05:30

368 lines
12 KiB
Swift

import Foundation
/// Synor DEX SDK Client
///
/// Complete decentralized exchange client with support for:
/// - AMM swaps (constant product, stable, concentrated)
/// - Liquidity provision
/// - Perpetual futures (up to 100x leverage)
/// - Order books (limit orders)
/// - Farming & staking
public class SynorDex {
private let config: DexConfig
private let session: URLSession
private var closed = false
public let perps: PerpsClient
public let orderbook: OrderBookClient
public let farms: FarmsClient
public init(config: DexConfig) {
self.config = config
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = TimeInterval(config.timeout) / 1000
self.session = URLSession(configuration: configuration)
self.perps = PerpsClient()
self.orderbook = OrderBookClient()
self.farms = FarmsClient()
self.perps.dex = self
self.orderbook.dex = self
self.farms.dex = self
}
// MARK: - Token Operations
public func getToken(address: String) async throws -> Token {
try await get("/tokens/\(address)")
}
public func listTokens() async throws -> [Token] {
try await get("/tokens")
}
public func searchTokens(query: String) async throws -> [Token] {
let encoded = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? query
return try await get("/tokens/search?q=\(encoded)")
}
// MARK: - Pool Operations
public func getPool(tokenA: String, tokenB: String) async throws -> Pool {
try await get("/pools/\(tokenA)/\(tokenB)")
}
public func getPoolById(poolId: String) async throws -> Pool {
try await get("/pools/\(poolId)")
}
public func listPools(filter: PoolFilter? = nil) async throws -> [Pool] {
var params: [String] = []
if let f = filter {
if let tokens = f.tokens { params.append("tokens=\(tokens.joined(separator: ","))") }
if let minTvl = f.minTvl { params.append("min_tvl=\(minTvl)") }
if let minVolume = f.minVolume24h { params.append("min_volume=\(minVolume)") }
if let verified = f.verified { params.append("verified=\(verified)") }
if let limit = f.limit { params.append("limit=\(limit)") }
if let offset = f.offset { params.append("offset=\(offset)") }
}
let path = params.isEmpty ? "/pools" : "/pools?\(params.joined(separator: "&"))"
return try await get(path)
}
// MARK: - Swap Operations
public func getQuote(params: QuoteParams) async throws -> Quote {
try await post("/swap/quote", body: [
"token_in": params.tokenIn,
"token_out": params.tokenOut,
"amount_in": String(params.amountIn),
"slippage": params.slippage ?? 0.005
])
}
public func swap(params: SwapParams) async throws -> SwapResult {
let deadline = params.deadline ?? Int(Date().timeIntervalSince1970) + 1200
var body: [String: Any] = [
"token_in": params.tokenIn,
"token_out": params.tokenOut,
"amount_in": String(params.amountIn),
"min_amount_out": String(params.minAmountOut),
"deadline": deadline
]
if let recipient = params.recipient {
body["recipient"] = recipient
}
return try await post("/swap", body: body)
}
// MARK: - Liquidity Operations
public func addLiquidity(params: AddLiquidityParams) async throws -> LiquidityResult {
let deadline = params.deadline ?? Int(Date().timeIntervalSince1970) + 1200
var body: [String: Any] = [
"token_a": params.tokenA,
"token_b": params.tokenB,
"amount_a": String(params.amountA),
"amount_b": String(params.amountB),
"deadline": deadline
]
if let minA = params.minAmountA { body["min_amount_a"] = String(minA) }
if let minB = params.minAmountB { body["min_amount_b"] = String(minB) }
return try await post("/liquidity/add", body: body)
}
public func removeLiquidity(params: RemoveLiquidityParams) async throws -> LiquidityResult {
let deadline = params.deadline ?? Int(Date().timeIntervalSince1970) + 1200
var body: [String: Any] = [
"pool": params.pool,
"lp_amount": String(params.lpAmount),
"deadline": deadline
]
if let minA = params.minAmountA { body["min_amount_a"] = String(minA) }
if let minB = params.minAmountB { body["min_amount_b"] = String(minB) }
return try await post("/liquidity/remove", body: body)
}
public func getMyPositions() async throws -> [LPPosition] {
try await get("/liquidity/positions")
}
// MARK: - Analytics
public func getPriceHistory(pair: String, interval: String, limit: Int = 100) async throws -> [OHLCV] {
try await get("/analytics/candles/\(pair)?interval=\(interval)&limit=\(limit)")
}
public func getTradeHistory(pair: String, limit: Int = 50) async throws -> [TradeHistory] {
try await get("/analytics/trades/\(pair)?limit=\(limit)")
}
public func getVolumeStats() async throws -> VolumeStats {
try await get("/analytics/volume")
}
public func getTVL() async throws -> TVLStats {
try await get("/analytics/tvl")
}
// MARK: - Lifecycle
public func healthCheck() async -> Bool {
do {
let response: HealthResponse = try await get("/health")
return response.status == "healthy"
} catch {
return false
}
}
public func close() {
closed = true
session.invalidateAndCancel()
}
public var isClosed: Bool { closed }
// MARK: - Internal Methods
func get<T: Decodable>(_ path: String) async throws -> T {
try checkClosed()
var request = URLRequest(url: URL(string: config.endpoint + path)!)
request.httpMethod = "GET"
addHeaders(&request)
return try await execute(request)
}
func post<T: Decodable>(_ path: String, body: [String: Any]) async throws -> T {
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 execute(request)
}
func delete<T: Decodable>(_ path: String) async throws -> T {
try checkClosed()
var request = URLRequest(url: URL(string: config.endpoint + path)!)
request.httpMethod = "DELETE"
addHeaders(&request)
return try await execute(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 execute<T: Decodable>(_ request: URLRequest) async throws -> T {
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw DexError.network("Invalid response")
}
guard (200...299).contains(httpResponse.statusCode) else {
let error = try? JSONDecoder().decode(ErrorResponse.self, from: data)
throw DexError.http(
message: error?.message ?? "HTTP \(httpResponse.statusCode)",
code: error?.code,
status: httpResponse.statusCode
)
}
return try JSONDecoder().decode(T.self, from: data)
}
private func checkClosed() throws {
if closed {
throw DexError.clientClosed
}
}
private struct HealthResponse: Decodable {
let status: String
}
private struct ErrorResponse: Decodable {
let message: String?
let code: String?
}
}
/// DEX client configuration
public struct DexConfig {
public let apiKey: String
public let endpoint: String
public let wsEndpoint: String
public let timeout: Int
public let retries: Int
public let debug: Bool
public init(
apiKey: String,
endpoint: String = "https://dex.synor.io/v1",
wsEndpoint: String = "wss://dex.synor.io/v1/ws",
timeout: Int = 30000,
retries: Int = 3,
debug: Bool = false
) {
self.apiKey = apiKey
self.endpoint = endpoint
self.wsEndpoint = wsEndpoint
self.timeout = timeout
self.retries = retries
self.debug = debug
}
}
/// DEX SDK Error
public enum DexError: Error {
case http(message: String, code: String?, status: Int)
case network(String)
case clientClosed
}
/// Perpetual futures sub-client
public class PerpsClient {
weak var dex: SynorDex?
public func listMarkets() async throws -> [PerpMarket] {
try await dex!.get("/perps/markets")
}
public func getMarket(symbol: String) async throws -> PerpMarket {
try await dex!.get("/perps/markets/\(symbol)")
}
public func openPosition(params: OpenPositionParams) async throws -> PerpPosition {
var body: [String: Any] = [
"market": params.market,
"side": params.side.rawValue,
"size": String(params.size),
"leverage": params.leverage,
"order_type": params.orderType.rawValue,
"margin_type": params.marginType.rawValue,
"reduce_only": params.reduceOnly
]
if let price = params.limitPrice { body["limit_price"] = price }
if let sl = params.stopLoss { body["stop_loss"] = sl }
if let tp = params.takeProfit { body["take_profit"] = tp }
return try await dex!.post("/perps/positions", body: body)
}
public func closePosition(params: ClosePositionParams) async throws -> PerpPosition {
var body: [String: Any] = [
"market": params.market,
"order_type": params.orderType.rawValue
]
if let size = params.size { body["size"] = String(size) }
if let price = params.limitPrice { body["limit_price"] = price }
return try await dex!.post("/perps/positions/close", body: body)
}
public func getPositions() async throws -> [PerpPosition] {
try await dex!.get("/perps/positions")
}
public func getOrders() async throws -> [PerpOrder] {
try await dex!.get("/perps/orders")
}
public func getFundingHistory(market: String, limit: Int = 100) async throws -> [FundingPayment] {
try await dex!.get("/perps/funding/\(market)?limit=\(limit)")
}
}
/// Order book sub-client
public class OrderBookClient {
weak var dex: SynorDex?
public func getOrderBook(market: String, depth: Int = 20) async throws -> OrderBook {
try await dex!.get("/orderbook/\(market)?depth=\(depth)")
}
public func placeLimitOrder(params: LimitOrderParams) async throws -> Order {
try await dex!.post("/orderbook/orders", body: [
"market": params.market,
"side": params.side,
"price": params.price,
"size": String(params.size),
"time_in_force": params.timeInForce.rawValue,
"post_only": params.postOnly
])
}
public func getOpenOrders(market: String? = nil) async throws -> [Order] {
let path = market.map { "/orderbook/orders?market=\($0)" } ?? "/orderbook/orders"
return try await dex!.get(path)
}
}
/// Farms sub-client
public class FarmsClient {
weak var dex: SynorDex?
public func listFarms() async throws -> [Farm] {
try await dex!.get("/farms")
}
public func getFarm(farmId: String) async throws -> Farm {
try await dex!.get("/farms/\(farmId)")
}
public func stake(params: StakeParams) async throws -> FarmPosition {
try await dex!.post("/farms/stake", body: [
"farm": params.farm,
"amount": String(params.amount)
])
}
public func getMyFarmPositions() async throws -> [FarmPosition] {
try await dex!.get("/farms/positions")
}
}