mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-11-08 08:14:00 +01:00
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.
118 lines
3.6 KiB
Dart
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),
|
|
);
|
|
}
|
|
}
|