Files
hash-wallet/lib/anonpay/anonpay_api.dart
T
jwinterm 04203654f1 Rename Flutter project + binary: cake_wallet -> hash_wallet
The big one. Touches 760+ files. Locks in the user's decision to break
import-line compatibility with upstream cake-tech/cake_wallet in exchange
for a binary named hash_wallet on every platform.

Changes:
- pubspec_description.yaml: project name cake_wallet -> hash_wallet
- 762 Dart files: every 'package:cake_wallet/...' import rewritten to
  'package:hash_wallet/...' (lib/, cw_*/lib/, tool/, test/, integration_test/)
- linux/CMakeLists.txt: BINARY_NAME cake_wallet -> hash_wallet
- windows/CMakeLists.txt: project + BINARY_NAME (CakeWallet -> HashWallet)
- windows/runner/Runner.rc: company / file / product strings to Hash Wallet
  + Such Software LLC copyright
- macos/Runner.xcodeproj/project_base.pbxproj: cake_wallet_new.app refs
  -> hash_wallet.app (macOS output bundle name)
- com.suchsoftware.HashWallet.yml (Flatpak): all binary path refs
  cake_wallet -> hash_wallet (e.g. /app/hash_wallet/hash_wallet)
- linux/com.suchsoftware.HashWallet.desktop: Exec=hash_wallet
- scripts/android/copy_monero_deps.sh: CW_DIR target path
- configure_hash_wallet.sh: error message typo (old filename)

INTENTIONALLY UNCHANGED:
- cw_* subpackage names (cw_core, cw_bitcoin, cw_wownero, etc.) — they
  remain as the historical 'cake wallet' naming; renaming them would
  cascade through every sub-package's internal structure
- Method channel name 'com.cake_wallet/native_utils' in iOS/macOS/Android
  AppDelegates and MainActivity — it's a string identifier that has to
  match the Dart side exactly. Renaming requires both-sides change; it's
  internal-only and not user-visible.
- ghcr.io/cake-tech/cake_wallet:debian13-... docker image references in
  build scripts — that's Cake Labs' published image we use as the build
  environment, not ours.

User data directory now follows the new binary name:
  ~/.local/share/hash_wallet/  (was ~/.local/share/cake_wallet/)
  ~/.config/hash_wallet/       (was ~/.config/cake_wallet/)
Existing users on the old name will need migration if any exist.
2026-05-16 11:33:25 -04:00

206 lines
6.7 KiB
Dart

import 'dart:convert';
import 'package:hash_wallet/anonpay/anonpay_donation_link_info.dart';
import 'package:hash_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:hash_wallet/anonpay/anonpay_request.dart';
import 'package:hash_wallet/anonpay/anonpay_status_response.dart';
import 'package:hash_wallet/core/fiat_conversion_service.dart';
import 'package:hash_wallet/entities/fiat_currency.dart';
import 'package:hash_wallet/exchange/limits.dart';
import 'package:hash_wallet/wallet_type_utils.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:hash_wallet/.secrets.g.dart' as secrets;
class AnonPayApi {
const AnonPayApi({
this.useTorOnly = false,
required this.wallet,
});
final bool useTorOnly;
final WalletBase wallet;
static const anonpayRef = secrets.anonPayReferralCode;
static const clearNetAuthority = 'trocador.app';
// static const onionApiAuthority = 'tqzngtf2hybjbexznel6dhgsvbynjzezoybvtv6iofomx7gchqfssgqd.onion';
static const onionApiAuthority = clearNetAuthority;
static const markup = secrets.trocadorExchangeMarkup;
static const anonPayPath = '/anonpay';
static const anonPayStatus = '/anonpay/status';
static const coinPath = 'api/coin';
static final apiKey = isMoneroOnly ? secrets.trocadorMoneroApiKey : secrets.trocadorApiKey;
Future<AnonpayStatusResponse> paymentStatus(String id) async {
final response = await ProxyWrapper().get(
clearnetUri: Uri.https(clearNetAuthority, "$anonPayStatus/$id"),
onionUri: Uri.https(onionApiAuthority, "$anonPayStatus/$id"),
);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final status = responseJSON['Status'] as String;
final fiatAmount = responseJSON['Fiat_Amount'] as double?;
final fiatEquiv = responseJSON['Fiat_Equiv'] as String?;
final amountTo = responseJSON['AmountTo'] as double?;
final coinTo = responseJSON['CoinTo'] as String;
final address = responseJSON['Address'] as String;
return AnonpayStatusResponse(
status: status,
fiatAmount: fiatAmount,
amountTo: amountTo,
coinTo: coinTo,
address: address,
fiatEquiv: fiatEquiv,
);
}
Future<AnonpayInvoiceInfo> createInvoice(AnonPayRequest request) async {
final description = Uri.encodeComponent(request.description);
final body = <String, dynamic>{
'ticker_to': request.cryptoCurrency.title.toLowerCase(),
'network_to': _networkFor(request.cryptoCurrency),
'address': request.address,
'name': request.name,
'description': description,
'email': request.email,
'ref': anonpayRef,
'markup': markup,
'direct': 'False',
};
if (request.amount != null) {
body['amount'] = request.amount;
}
if (request.fiatEquivalent != null) {
body['fiat_equiv'] = request.fiatEquivalent;
}
final response = await ProxyWrapper().get(
clearnetUri: Uri.https(clearNetAuthority, anonPayPath, body),
onionUri: Uri.https(onionApiAuthority, anonPayPath, body),
);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['ID'] as String;
final url = responseJSON['url'] as String;
final urlOnion = responseJSON['url_onion'] as String;
final statusUrl = responseJSON['status_url'] as String;
final statusUrlOnion = responseJSON['status_url_onion'] as String;
final statusInfo = await paymentStatus(id);
return AnonpayInvoiceInfo(
invoiceId: id,
clearnetUrl: url,
onionUrl: urlOnion,
status: statusInfo.status,
fiatAmount: statusInfo.fiatAmount,
fiatEquiv: statusInfo.fiatEquiv,
amountTo: statusInfo.amountTo,
coinTo: statusInfo.coinTo,
address: statusInfo.address,
clearnetStatusUrl: statusUrl,
onionStatusUrl: statusUrlOnion,
walletId: wallet.id,
createdAt: DateTime.now(),
provider: 'Trocador AnonPay invoice',
);
}
Future<AnonpayDonationLinkInfo> generateDonationLink(AnonPayRequest request) async {
final body = <String, dynamic>{
'ticker_to': request.cryptoCurrency.title.toLowerCase(),
'network_to': _networkFor(request.cryptoCurrency),
'address': request.address,
'ref': anonpayRef,
'direct': 'True',
};
if (request.name.isNotEmpty) {
body['name'] = request.name;
}
if (request.description.isNotEmpty) {
body['description'] = request.description;
}
if (request.email.isNotEmpty) {
body['email'] = request.email;
}
final clearnetUrl = Uri.https(clearNetAuthority, anonPayPath, body);
final onionUrl = Uri.https(onionApiAuthority, anonPayPath, body);
return AnonpayDonationLinkInfo(
clearnetUrl: clearnetUrl.toString(),
onionUrl: onionUrl.toString(),
address: request.address,
);
}
Future<Limits> fetchLimits({
FiatCurrency? fiatCurrency,
required CryptoCurrency cryptoCurrency,
}) async {
double fiatRate = 0.0;
if (fiatCurrency != null) {
fiatRate = await FiatConversionService.fetchPrice(
crypto: cryptoCurrency,
fiat: fiatCurrency,
torOnly: useTorOnly,
);
}
final params = <String, String>{
'api_key': apiKey,
'ticker': cryptoCurrency.title.toLowerCase(),
'name': cryptoCurrency.name,
};
final response = await ProxyWrapper().get(
clearnetUri: Uri.https(clearNetAuthority, coinPath, params),
onionUri: Uri.https(onionApiAuthority, coinPath, params),
);
final responseJSON = json.decode(response.body) as List<dynamic>;
if (response.statusCode != 200) {
throw Exception('Unexpected http status: ${response.statusCode}');
}
if (responseJSON.isEmpty) {
throw Exception('No data');
}
final coinJson = responseJSON.first as Map<String, dynamic>;
final minimum = coinJson['minimum'] as double;
final maximum = coinJson['maximum'] as double;
if (fiatCurrency != null) {
return Limits(
min: double.tryParse((minimum * fiatRate).toStringAsFixed(2)),
max: double.tryParse((maximum * fiatRate).toStringAsFixed(2)),
);
}
return Limits(
min: minimum,
max: maximum,
);
}
String _networkFor(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.usdt:
return CryptoCurrency.btc.title.toLowerCase();
case CryptoCurrency.eth:
return 'ERC20';
default:
return currency.tag != null ? _normalizeTag(currency.tag!) : 'Mainnet';
}
}
String _normalizeTag(String tag) {
switch (tag) {
case 'ETH':
return 'ERC20';
default:
return tag.toLowerCase();
}
}
}