Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions cw_zano/lib/zano_wallet.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert' as convert;
import 'dart:core';
import 'dart:io';
import 'dart:math';
Expand Down Expand Up @@ -58,11 +59,6 @@ abstract class ZanoWalletBase
@override
String get password => _password;

@override
Future<String> signMessage(String message, {String? address = null}) {
throw UnimplementedError();
}

@override
Future<bool> verifyMessage(String message, String signature, {String? address = null}) {
throw UnimplementedError();
Expand Down
58 changes: 53 additions & 5 deletions cw_zano/lib/zano_wallet_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';

import 'package:cake_wallet/core/logger_service.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/zano_asset.dart';
Expand Down Expand Up @@ -46,10 +47,10 @@ mixin ZanoWalletApi {
void setPassword(String password) => zano.PlainWallet_resetWalletPassword(hWallet, password);

void closeWallet(int? walletToClose, {bool force = false}) async {
printV('close_wallet ${walletToClose ?? hWallet}: $force');
LoggerService.debug('close_wallet ${walletToClose ?? hWallet}: $force', tag: 'Zano');
if (Platform.isWindows || force) {
final result = await _closeWallet(walletToClose ?? hWallet);
printV('close_wallet result $result');
LoggerService.debug('close_wallet result $result', tag: 'Zano');
openWalletCache.removeWhere((_, cwr) => cwr.walletId == (walletToClose ?? hWallet));
}
}
Expand Down Expand Up @@ -133,14 +134,14 @@ mixin ZanoWalletApi {
Future<GetWalletInfoResult> getWalletInfo() async {
final json = await _getWalletInfo(hWallet);
final result = GetWalletInfoResult.fromJson(jsonDecode(json));
printV('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances}');
LoggerService.debug('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances}', tag: 'Zano');
return result;
}

Future<GetWalletStatusResult> getWalletStatus() async {
final json = await _getWalletStatus(hWallet);
if (json == Consts.errorWalletWrongId) {
printV('wrong wallet id');
LoggerService.error('wrong wallet id', tag: 'Zano');
throw ZanoWalletException('Wrong wallet id');
}
final status = GetWalletStatusResult.fromJson(jsonDecode(json));
Expand All @@ -160,7 +161,7 @@ mixin ZanoWalletApi {
jsonDecode(invokeResult);
} catch (e) {
if (invokeResult.contains(Consts.errorWalletWrongId)) throw ZanoWalletException('Wrong wallet id');
printV('exception in parsing json in invokeMethod: $invokeResult');
LoggerService.error('exception in parsing json in invokeMethod: $invokeResult', tag: 'Zano');
rethrow;
}
return invokeResult;
Expand Down Expand Up @@ -230,6 +231,53 @@ mixin ZanoWalletApi {
return ProxyToDaemonResult.fromJson(map!['result'] as Map<String, dynamic>);
}

@override
Future<String> signMessage(String message, {String? address}) async {
try {
final messageBase64 = convert.base64.encode(convert.utf8.encode(message));

final response = await invokeMethod('sign_message', {
'buff': messageBase64
});

final responseData = jsonDecode(response) as Map<String, dynamic>;

// Check for top-level errors first
if (responseData['error'] != null) {
final error = responseData['error'];
final code = error['code'] ?? '';
final message = error['message'] ?? 'Unknown error';
throw ZanoWalletException('Sign message failed: $message ($code)');
}

final result = responseData['result'] as Map<String, dynamic>?;
if (result == null) {
throw ZanoWalletException('Invalid response from sign_message: no result');
}

final signature = result['sig'] as String?;
if (signature == null) {
throw ZanoWalletException('No signature in response');
}

// Basic validation: signature should be hex and have expected length
if (!RegExp(r'^[0-9a-fA-F]+$').hasMatch(signature)) {
throw ZanoWalletException('Invalid signature format: not hexadecimal');
}

// Validate signature length (64 bytes = 128 hex chars)
const int expectedSignatureLength = 128;
if (signature.length != expectedSignatureLength) {
LoggerService.warning('Unexpected signature length', tag: 'Zano');
}

return signature;
} catch (e) {
if (e is ZanoWalletException) rethrow;
throw ZanoWalletException('Failed to sign message: $e');
}
}

Future<ZanoAsset?> getAssetInfo(String assetId) async {
final methodName = 'get_asset_info';
final params = AssetIdParams(assetId: assetId);
Expand Down
200 changes: 200 additions & 0 deletions lib/buy/buy_provider_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import 'package:cake_wallet/entities/fiat_currency.dart';

/// Configuration for buy/sell provider features
class BuyProviderConfig {
// Singleton instance
static BuyProviderConfig? _instance;

factory BuyProviderConfig() {
_instance ??= BuyProviderConfig._internal();
return _instance!;
}

BuyProviderConfig._internal();

/// Dispose of the singleton instance and free resources
static void dispose() {
_instance?._cleanup();
_instance = null;
}

void _cleanup() {
// Clear all cached data
_currencyFallbacks.clear();
// Reset to defaults to free any held references
_quoteTimeoutSeconds = 10;
_authenticationTimeoutSeconds = 30;
_providerLaunchTimeoutSeconds = 60;
_maxRetryAttempts = 3;
_retryDelayMilliseconds = 1000;
_quoteCacheDurationSeconds = 30;
_enableQuoteCache = true;
}

// Currency fallback configuration
final Map<FiatCurrency, List<FiatCurrency>> _currencyFallbacks = {
FiatCurrency.usd: [FiatCurrency.eur, FiatCurrency.gbp],
FiatCurrency.cad: [FiatCurrency.usd, FiatCurrency.eur],
FiatCurrency.aud: [FiatCurrency.usd, FiatCurrency.eur],
FiatCurrency.chf: [FiatCurrency.eur, FiatCurrency.usd],
FiatCurrency.jpy: [FiatCurrency.usd, FiatCurrency.eur],
};

// Timeout configuration (in seconds)
int _quoteTimeoutSeconds = 10;
int _authenticationTimeoutSeconds = 30;
int _providerLaunchTimeoutSeconds = 60;

// Retry configuration
int _maxRetryAttempts = 3;
int _retryDelayMilliseconds = 1000;

// Cache configuration
int _quoteCacheDurationSeconds = 30;
bool _enableQuoteCache = true;

// Getters
List<FiatCurrency> getFallbackCurrencies(FiatCurrency from) {
return _currencyFallbacks[from] ?? [];
}

Duration get quoteTimeout => Duration(seconds: _quoteTimeoutSeconds);
Duration get authenticationTimeout => Duration(seconds: _authenticationTimeoutSeconds);
Duration get providerLaunchTimeout => Duration(seconds: _providerLaunchTimeoutSeconds);

int get maxRetryAttempts => _maxRetryAttempts;
Duration get retryDelay => Duration(milliseconds: _retryDelayMilliseconds);

Duration get quoteCacheDuration => Duration(seconds: _quoteCacheDurationSeconds);
bool get isQuoteCacheEnabled => _enableQuoteCache;

// Setters for runtime configuration
void setQuoteTimeout(int seconds) {
if (seconds > 0 && seconds <= 120) {
_quoteTimeoutSeconds = seconds;
}
}

void setAuthenticationTimeout(int seconds) {
if (seconds > 0 && seconds <= 120) {
_authenticationTimeoutSeconds = seconds;
}
}

void setMaxRetryAttempts(int attempts) {
if (attempts >= 0 && attempts <= 10) {
_maxRetryAttempts = attempts;
}
}

void setRetryDelay(int milliseconds) {
if (milliseconds >= 0 && milliseconds <= 10000) {
_retryDelayMilliseconds = milliseconds;
}
}

void setQuoteCacheDuration(int seconds) {
if (seconds >= 0 && seconds <= 300) {
_quoteCacheDurationSeconds = seconds;
}
}

void setQuoteCacheEnabled(bool enabled) {
_enableQuoteCache = enabled;
}

void addCurrencyFallback(FiatCurrency from, FiatCurrency to) {
if (!_currencyFallbacks.containsKey(from)) {
_currencyFallbacks[from] = [];
}
if (!_currencyFallbacks[from]!.contains(to)) {
_currencyFallbacks[from]!.add(to);
}
}

void removeCurrencyFallback(FiatCurrency from, FiatCurrency to) {
_currencyFallbacks[from]?.remove(to);
}

void clearCurrencyFallbacks(FiatCurrency from) {
_currencyFallbacks[from]?.clear();
}

// Reset to defaults
void resetToDefaults() {
_quoteTimeoutSeconds = 10;
_authenticationTimeoutSeconds = 30;
_providerLaunchTimeoutSeconds = 60;
_maxRetryAttempts = 3;
_retryDelayMilliseconds = 1000;
_quoteCacheDurationSeconds = 30;
_enableQuoteCache = true;

_currencyFallbacks.clear();
_currencyFallbacks[FiatCurrency.usd] = [FiatCurrency.eur, FiatCurrency.gbp];
_currencyFallbacks[FiatCurrency.cad] = [FiatCurrency.usd, FiatCurrency.eur];
_currencyFallbacks[FiatCurrency.aud] = [FiatCurrency.usd, FiatCurrency.eur];
_currencyFallbacks[FiatCurrency.chf] = [FiatCurrency.eur, FiatCurrency.usd];
_currencyFallbacks[FiatCurrency.jpy] = [FiatCurrency.usd, FiatCurrency.eur];
}

// Load from JSON (for persistence)
void loadFromJson(Map<String, dynamic> json) {
if (json['quoteTimeoutSeconds'] != null) {
setQuoteTimeout(json['quoteTimeoutSeconds'] as int);
}
if (json['authenticationTimeoutSeconds'] != null) {
setAuthenticationTimeout(json['authenticationTimeoutSeconds'] as int);
}
if (json['maxRetryAttempts'] != null) {
setMaxRetryAttempts(json['maxRetryAttempts'] as int);
}
if (json['retryDelayMilliseconds'] != null) {
setRetryDelay(json['retryDelayMilliseconds'] as int);
}
if (json['quoteCacheDurationSeconds'] != null) {
setQuoteCacheDuration(json['quoteCacheDurationSeconds'] as int);
}
if (json['enableQuoteCache'] != null) {
setQuoteCacheEnabled(json['enableQuoteCache'] as bool);
}

// Load currency fallbacks
if (json['currencyFallbacks'] != null) {
_currencyFallbacks.clear();
final fallbacks = json['currencyFallbacks'] as Map<String, dynamic>;
fallbacks.forEach((key, value) {
final fromCurrency = FiatCurrency.all.firstWhere(
(e) => e.toString() == key,
orElse: () => FiatCurrency.usd,
);
final toCurrencies = (value as List<dynamic>).map((e) {
return FiatCurrency.all.firstWhere(
(c) => c.toString() == e.toString(),
orElse: () => FiatCurrency.eur,
);
}).toList();
_currencyFallbacks[fromCurrency] = toCurrencies;
});
}
}

// Save to JSON (for persistence)
Map<String, dynamic> toJson() {
final fallbacksJson = <String, dynamic>{};
_currencyFallbacks.forEach((key, value) {
fallbacksJson[key.toString()] = value.map((e) => e.toString()).toList();
});

return {
'quoteTimeoutSeconds': _quoteTimeoutSeconds,
'authenticationTimeoutSeconds': _authenticationTimeoutSeconds,
'providerLaunchTimeoutSeconds': _providerLaunchTimeoutSeconds,
'maxRetryAttempts': _maxRetryAttempts,
'retryDelayMilliseconds': _retryDelayMilliseconds,
'quoteCacheDurationSeconds': _quoteCacheDurationSeconds,
'enableQuoteCache': _enableQuoteCache,
'currencyFallbacks': fallbacksJson,
};
}
}
Loading
Loading