72 lines
2.5 KiB
Swift
72 lines
2.5 KiB
Swift
import Foundation
|
|
|
|
/// Minimal XRP Ledger JSON-RPC client. A couple of public endpoints are tried
|
|
/// in order so a single outage does not break the wallet.
|
|
final class XRPClient {
|
|
static let shared = XRPClient()
|
|
|
|
private let endpoints = [
|
|
"https://s1.ripple.com:51234/",
|
|
"https://s2.ripple.com:51234/",
|
|
"https://xrplcluster.com/",
|
|
]
|
|
|
|
private var preferredEndpoint = 0
|
|
private let session: URLSession
|
|
|
|
private init() {
|
|
let configuration = URLSessionConfiguration.ephemeral
|
|
configuration.timeoutIntervalForRequest = 20
|
|
configuration.waitsForConnectivity = false
|
|
session = URLSession(configuration: configuration)
|
|
}
|
|
|
|
func rpc(_ method: String, _ params: [String: Any] = [:]) async throws -> [String: Any] {
|
|
var lastError: Error = XRPError.network("XRP network is unavailable")
|
|
for attempt in 0..<endpoints.count {
|
|
let index = (preferredEndpoint + attempt) % endpoints.count
|
|
guard let url = URL(string: endpoints[index]) else { continue }
|
|
do {
|
|
let result = try await send(method, params, to: url)
|
|
preferredEndpoint = index
|
|
return result
|
|
} catch {
|
|
lastError = error
|
|
}
|
|
}
|
|
throw lastError
|
|
}
|
|
|
|
private func send(_ method: String, _ params: [String: Any], to url: URL) async throws -> [String: Any] {
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "POST"
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
let body: [String: Any] = ["method": method, "params": [params], "id": 1]
|
|
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
|
|
|
let (data, response) = try await session.data(for: request)
|
|
guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
|
|
throw XRPError.network("Ledger request failed (\(method))")
|
|
}
|
|
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
throw XRPError.network("Malformed ledger response")
|
|
}
|
|
|
|
let result = json["result"] as? [String: Any] ?? [:]
|
|
if let error = result["error"] as? String {
|
|
let message = result["error_message"] as? String ?? error
|
|
throw XRPError.rpc(message)
|
|
}
|
|
if let error = json["error"] as? String {
|
|
throw XRPError.rpc(json["error_message"] as? String ?? error)
|
|
}
|
|
return result
|
|
}
|
|
|
|
static func isAccountNotFound(_ error: Error) -> Bool {
|
|
if case XRPError.rpc(let message) = error {
|
|
return message == "actNotFound" || message.contains("Account not found")
|
|
}
|
|
return false
|
|
}
|
|
} |