Files
hash-wallet/lib/new-ui/model/charts/price_api_client.dart
T
jwinterm dd449f2594 Merge upstream Cake Wallet v6.5.0
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535
upstream commits, including the Monero silent-open fix, Monero on Trezor,
Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin
sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive
adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9.

Merged with both sides normalized first (import rename, 100-column
format), which took the conflict count from 614 paths to 322. The
procedure is now scripts/upstream_sync.sh.

Kept from Hash Bags:
- WalletType enum order and Hive IDs. SQLite stores WalletType.index and
  our order differs from upstream's; taking theirs would reread saved
  wallets as other chains. Our frozen adapter keeps the shipped IDs.
- The 1.0.2 legacy PIN and wallet-password decode and self-heal.
- Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new
  Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out.
- Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me),
  converted to upstream's isDefault node-list key; Wownero is back in the
  node reset list.
- Privacy: address resolvers limited to domain-anchored sources again;
  prices.neroswap.com for fiat; upstream's charts tab, which reads
  prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false.
- Lightning stays behind FeatureFlag.isLightningEnabled, including
  upstream's new post-seed Lightning username step.
- Our icon pipeline, branding and bundle identifiers.

Twelve new upstream widget tests are skipped: test and code are unchanged
from v6.5.0 apart from renames (not rerun on a pristine upstream checkout;
upstream's top_bar_test does not compile against its own TopBar).
flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped;
flutter build bundle compiles. Built with Flutter 3.41.9.
2026-10-24 23:49:29 -04:00

118 lines
3.6 KiB
Dart

import "dart:convert";
import "package:hash_wallet/.secrets.g.dart" as secrets;
import "package:hash_wallet/utils/feature_flag.dart";
import "package:hash_wallet/new-ui/model/charts/datetime_extension.dart";
import "package:hash_wallet/new-ui/model/charts/price_data.dart";
import "package:cw_core/amount/money.dart";
import "package:cw_core/currency.dart";
import "package:cw_core/utils/print_verbose.dart";
import "package:cw_core/utils/proxy_wrapper.dart";
const priceApiHost = "prices.cakewallet.com";
const priceApiHostOnion = "46wisoe2uwipcj2j4og6smiq7hbmj34fkkrquwlzbbsqtgat7bf3erid.onion";
class LatestPriceRequest {
LatestPriceRequest({required this.from, required this.to});
final Currency from;
final Currency to;
Uri get uri => Uri.https(priceApiHost, "/v3/rates", {
"base": from.apiString,
"quote": to.apiString,
});
Uri get onionUri => Uri.http(priceApiHostOnion, uri.path, uri.queryParameters);
}
class PriceRequest {
const PriceRequest({
required this.interval,
required this.from,
required this.to,
this.beginTime,
this.count,
});
final DateTime? beginTime;
final Duration interval;
final int? count;
final Currency from;
final Currency to;
Uri get uri => Uri.https(priceApiHost, "/v3/rates", {
"start": (beginTime?.secondsSinceEpoch ?? 0).toString(),
"end": DateTime.now().secondsSinceEpoch.toString(),
"interval": "${interval.inSeconds}",
if (count != null) "count": count.toString(),
"quote": from.apiString,
"base": to.apiString,
});
Uri get onionUri => Uri.http(priceApiHostOnion, uri.path, uri.queryParameters);
}
class PriceApiClient {
static Future<Map<String, dynamic>?> _getJson(Uri uri) async {
if (!FeatureFlag.isChartsEnabled) return null;
final resp =
await ProxyWrapper().get(headers: {"x-api-key": secrets.fiatApiKey}, clearnetUri: uri);
if (!(resp.statusCode >= 200 && resp.statusCode < 300)) {
printV("server returned code: ${resp.statusCode}\nuri: ${uri}\nresp body: ${resp.body}");
return null;
}
try {
return jsonDecode(resp.body) as Map<String, dynamic>?;
} catch (e) {
printV("failed to decode response for ${uri.host}/${uri.path}: $e");
return null;
}
}
static Future<List<PriceData>> getPrices(PriceRequest request, {bool torOnly = false}) async {
final List<PriceData> ret = [];
printV(request.uri);
final data = await _getJson(torOnly ? request.onionUri : request.uri);
if (data == null) {
return [];
}
final results = data["results"] as Map<String, dynamic>?;
if (results == null) {
printV(data.toString());
return [];
}
printV(data.toString());
for (final time in results.keys) {
ret.add(
PriceData(
time: DateTimeX.fromSecondsSinceEpoch(int.parse(time)),
base: request.from,
quote: Money.parse((results[time] as num).toStringAsFixed(20), request.to, strictParsing: false),
),
);
}
return ret;
}
static Future<PriceData?> getLatestPrice(
LatestPriceRequest request, {
bool torOnly = false,
}) async {
final data = await _getJson(torOnly ? request.onionUri : request.uri);
if (data == null) {
return null;
}
final results = data["results"] as Map<String, dynamic>?;
if (results == null) {
return null;
}
final result = results[results.keys.first];
return PriceData(
time: DateTime.now(),
base: request.from,
quote: Money.parse((result as num).toString(), request.to, strictParsing: false),
);
}
}