From 2ee31b5ad70558ca7f90585e06f46828a5d49c3e Mon Sep 17 00:00:00 2001 From: sl Date: Tue, 19 May 2026 16:45:58 +0200 Subject: [PATCH] ios: use simple price for widget current price --- .../BitBoxAppWidget/WidgetDataService.swift | 40 +++++++++++++++++-- frontends/ios/WIDGET.md | 22 +++++++--- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/frontends/ios/BitBoxApp/BitBoxAppWidget/WidgetDataService.swift b/frontends/ios/BitBoxApp/BitBoxAppWidget/WidgetDataService.swift index 17d96ae16f..57e1b44f0f 100644 --- a/frontends/ios/BitBoxApp/BitBoxAppWidget/WidgetDataService.swift +++ b/frontends/ios/BitBoxApp/BitBoxAppWidget/WidgetDataService.swift @@ -64,6 +64,8 @@ struct WidgetDataService { return nil } + async let currentPriceFromSimplePrice = fetchCurrentPrice(coinCode: coinCode, currency: currency) + do { let (data, response) = try await session.data(from: url) let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1 @@ -85,18 +87,21 @@ struct WidgetDataService { return nil } - guard let currentPrice = prices.last, + guard let rangeCurrentPrice = prices.last, let firstPrice = prices.first, - currentPrice.isFinite, + rangeCurrentPrice.isFinite, firstPrice.isFinite, firstPrice != 0 else { return nil } + let simplePrice = await currentPriceFromSimplePrice + let currentPrice = simplePrice ?? rangeCurrentPrice + let chartPrices = simplePrice.map { prices + [$0] } ?? prices let change = (currentPrice - firstPrice) / firstPrice * 100 let result = PriceData( price: currentPrice, change24h: change, - chartPrices: prices, + chartPrices: chartPrices, coinCode: WidgetShared.normalizeCoinCode(coinCode), currency: currency.uppercased() ) @@ -107,6 +112,30 @@ struct WidgetDataService { } } + private func fetchCurrentPrice(coinCode: String, currency: String) async -> Double? { + guard let url = simplePriceURL(for: coinCode, currency: currency) else { + return nil + } + + do { + let (data, response) = try await session.data(from: url) + let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1 + guard statusCode == 200, + let decoded = try? JSONDecoder().decode([String: [String: Double]].self, from: data) else { + return nil + } + let geckoID = WidgetCoinMetadata.geckoID(for: coinCode) + let geckoCurrency = currency.lowercased() + guard let currentPrice = decoded[geckoID]?[geckoCurrency], + currentPrice.isFinite else { + return nil + } + return currentPrice + } catch { + return nil + } + } + private func chartURL(for coinCode: String, currency: String) -> URL? { let now = Int(Date().timeIntervalSince1970) let oneDayAgo = now - Self.chartRangeSeconds @@ -114,6 +143,11 @@ struct WidgetDataService { return URL(string: "https://exchangerates.shiftcrypto.io/api/v3/coins/\(geckoID)/market_chart/range?vs_currency=\(currency.lowercased())&from=\(oneDayAgo)&to=\(now)") } + private func simplePriceURL(for coinCode: String, currency: String) -> URL? { + let geckoID = WidgetCoinMetadata.geckoID(for: coinCode) + return URL(string: "https://exchangerates.shiftcrypto.io/api/v3/simple/price?ids=\(geckoID)&vs_currencies=\(currency.lowercased())") + } + private static func makeSession() -> URLSession { let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 10 diff --git a/frontends/ios/WIDGET.md b/frontends/ios/WIDGET.md index 36ad5e0f81..da78ada6ca 100644 --- a/frontends/ios/WIDGET.md +++ b/frontends/ios/WIDGET.md @@ -46,7 +46,8 @@ The widget shows **one coin at a time**. The user switches coins with left/right ## 3. How prices are fetched -The widget calls the **Shift Crypto CoinGecko Mirror API**: +The widget calls the **Shift Crypto CoinGecko Mirror API**. It uses the chart endpoint for historical +data: ``` https://exchangerates.shiftcrypto.io/api/v3/coins/{geckoID}/market_chart/range @@ -55,15 +56,24 @@ https://exchangerates.shiftcrypto.io/api/v3/coins/{geckoID}/market_chart/range &to={now} ``` +It also calls the simple price endpoint for a fresher current price: + +``` +https://exchangerates.shiftcrypto.io/api/v3/simple/price + ?ids={geckoID} + &vs_currencies={currency} +``` + Where `geckoID` maps `btc` -> `bitcoin`, `ltc` -> `litecoin`, `eth` -> `ethereum`. -This returns 24 hours of price data points. From that response: +The chart response returns 24 hours of price data points. The simple price response is appended to +that series when available. From those responses: -- **Current price** = last data point. -- **24-hour change** = percentage difference between first and last data points. -- **Chart data** = all returned price points (used for the sparkline). +- **Current price** = simple price when available, otherwise the chart response's last data point. +- **24-hour change** = percentage difference between the chart response's first data point and the current price. +- **Chart data** = all returned chart price points plus the simple price when available (used for the sparkline). -The request has a **10-second timeout**. +Each request has a **10-second timeout**. ---