원클릭으로
networking-guide
HTTP networking layer reference — TargetType, HTTPClient, services, error handling.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
HTTP networking layer reference — TargetType, HTTPClient, services, error handling.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Implement UI from Figma designs via MCP with a closed-loop pixel-parity check. Extract exact values (never eyeball), translate to SwiftUI, then render→diff→fix against the Figma export until it matches. Use when implementing or auditing UI against Figma.
Fetch CodeRabbit review comments from batch PRs and auto-fix the issues. Pushes fixes and re-requests review.
Run multiple tasks overnight as parallel PRs. Produces a PRD per task, selects skills/rules, spawns isolated agents, and reports PR links.
Run multiple tasks overnight as parallel PRs. Produces a PRD per task, selects skills/rules, spawns isolated agents, and reports PR links.
Run SwiftLint and xcodebuild to verify the project compiles cleanly.
Create a GitHub issue for vultisig-ios using bug/feature templates.
| name | networking-guide |
| description | HTTP networking layer reference — TargetType, HTTPClient, services, error handling. |
| user-invocable | false |
All networking uses a custom HTTP client built on URLSession with async/await.
Do not call URLSession or construct URLRequest outside Core/Networking/. SwiftLint custom rules (no_raw_urlsession, no_raw_urlrequest) flag violations as warnings today and will flip to errors once the Architecture 3 HTTPClient migration completes. If you see those warnings in a file you're editing, define a TargetType and route the call through HTTPClient.
Key files (all under VultisigApp/VultisigApp/Core/Networking/):
| File | Purpose |
|---|---|
TargetType.swift | Endpoint definition protocol + HTTPTask + ParameterEncoding + ValidationType |
HTTPClient.swift | URLSession wrapper implementation |
HTTPClientProtocol.swift | Protocol for dependency injection |
HTTPError.swift | Error types + HTTPResponse struct |
HTTPMethod.swift | HTTP methods enum |
public protocol TargetType {
var baseURL: URL { get }
var path: String { get }
var method: HTTPMethod { get }
var task: HTTPTask { get }
var headers: [String: String]? { get }
var validationType: ValidationType { get } // default: .successCodes
var timeoutInterval: TimeInterval { get } // default: 60.0
}
// Default header: ["Content-Type": "application/json"]
public enum HTTPTask {
case requestPlain // No parameters
case requestParameters([String: Any], ParameterEncoding) // Dictionary params
case requestData(Data) // Raw data body
case requestCompositeData(bodyData: Data, urlParameters: [String: Any]) // Body + URL params
case requestCodable(Encodable, ParameterEncoding) // Codable object
}
public enum ParameterEncoding {
case urlEncoding // Query parameters (GET requests)
case jsonEncoding // JSON body (POST/PUT/PATCH)
case formEncoding // Form data (application/x-www-form-urlencoded)
}
public enum ValidationType {
case noValidation
case successCodes // 200-299 (default)
case customCodes([Int])
}
public enum HTTPMethod: String, CaseIterable {
case get = "GET"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
case head = "HEAD"
case options = "OPTIONS"
}
protocol HTTPClientProtocol {
func request(_ target: TargetType) async throws -> HTTPResponse<Data>
func request<T: Decodable>(_ target: TargetType, responseType: T.Type) async throws -> HTTPResponse<T>
func requestEmpty(_ target: TargetType) async throws -> HTTPResponse<EmptyResponse>
}
enum HTTPError: Error, LocalizedError {
case invalidURL
case noData
case invalidResponse
case statusCode(Int, Data?)
case encodingFailed
case decodingFailed(Error)
case networkError(Error)
case timeout
case invalidSSLCertificate
}
struct HTTPResponse<T> {
let data: T
let response: HTTPURLResponse
}
Define endpoints as a TargetType enum:
// Services/[Feature]/[Feature]API.swift
enum MyFeatureAPI: TargetType {
case getData(id: String)
case postData(payload: MyPayload)
var baseURL: URL {
URL(string: "https://api.example.com")!
}
var path: String {
switch self {
case .getData(let id): return "/data/\(id)"
case .postData: return "/data"
}
}
var method: HTTPMethod {
switch self {
case .getData: return .get
case .postData: return .post
}
}
var task: HTTPTask {
switch self {
case .getData: return .requestPlain
case .postData(let payload): return .requestCodable(payload, .jsonEncoding)
}
}
var headers: [String: String]? {
["X-Client-ID": "vultisig"]
}
}
Services use constructor injection:
// Services/[Feature]/[Feature]Service.swift
struct MyFeatureService {
let httpClient: HTTPClientProtocol
init(httpClient: HTTPClientProtocol = HTTPClient()) {
self.httpClient = httpClient
}
func getData(id: String) async throws -> MyModel {
let response = try await httpClient.request(
MyFeatureAPI.getData(id: id),
responseType: MyModel.self
)
return response.data
}
}
Use domain-specific error enums:
enum MyFeatureError: Error, LocalizedError {
case notFound
case invalidData
var errorDescription: String? {
switch self {
case .notFound: return "Resource not found"
case .invalidData: return "Invalid data received"
}
}
}
File: Blockchain/THORChain/API/TargetType/THORChainAPI.swift
enum THORChainAPI: TargetType {
case getThornameDetails(name: String)
case getPools
case getPoolAsset(asset: String)
case getLastBlock
case getNetworkFees
case getConstants
// ...
var baseURL: URL {
switch self {
case .getThornameDetails, .getPools, .getPoolAsset, .getLastBlock, .getNetworkFees, .getConstants:
return URL(string: "https://thornode.ninerealms.com/thorchain")!
case .getThornameLookup, .getAddressLookup, .getHealth, .getNetworkInfo:
return URL(string: "https://midgard.ninerealms.com")!
}
}
var method: HTTPMethod { .get }
var task: HTTPTask { .requestPlain }
}
Service: Blockchain/THORChain/API/THORChainAPIService.swift
struct THORChainAPIService {
let httpClient: HTTPClientProtocol
let cache = THORChainAPICache()
init(httpClient: HTTPClientProtocol = HTTPClient()) {
self.httpClient = httpClient
}
func getPools() async throws -> [THORChainPoolResponse] {
if let cached = await cache.getCachedPools() { return cached }
let response = try await httpClient.request(
THORChainAPI.getPools,
responseType: [THORChainPoolResponse].self
)
await cache.cachePools(response.data)
return response.data
}
}
File: Blockchain/Tron/Service/TronAPI.swift
enum TronAPI: TargetType {
case getNowBlock
case getAccount(address: String)
case broadcastTransaction(jsonString: String)
case triggerConstantContract(ownerAddress: String, contractAddress: String,
functionSelector: String, parameter: String)
var method: HTTPMethod {
switch self {
case .getNowBlock, .getChainParameters: return .get
case .getAccount, .broadcastTransaction, .triggerConstantContract: return .post
}
}
var task: HTTPTask {
switch self {
case .getNowBlock: return .requestPlain
case .getAccount(let address):
return .requestParameters(["address": address, "visible": true], .jsonEncoding)
case .broadcastTransaction(let jsonString):
guard let data = jsonString.data(using: .utf8) else { return .requestPlain }
return .requestData(data)
case .triggerConstantContract(let owner, let contract, let selector, let param):
return .requestParameters([
"owner_address": owner, "contract_address": contract,
"function_selector": selector, "parameter": param, "visible": true
], .jsonEncoding)
}
}
}
actor MyCache {
private var cachedData: [Item]?
private var lastFetch: Date?
private let cacheDuration: TimeInterval = 300
func getCachedData() -> [Item]? {
guard let data = cachedData, let lastFetch,
Date().timeIntervalSince(lastFetch) < cacheDuration
else { return nil }
return data
}
func cacheData(_ data: [Item]) {
cachedData = data
lastFetch = Date()
}
}
Reference: Blockchain/THORChain/API/THORChainAPICache.swift