2023-07-24 20:23:09 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:ffi';
|
|
|
|
import 'package:ffi/ffi.dart';
|
|
|
|
import 'package:cw_monero/api/structs/ut8_box.dart';
|
|
|
|
import 'package:cw_monero/api/convert_utf8_to_string.dart';
|
|
|
|
import 'package:cw_monero/api/signatures.dart';
|
|
|
|
import 'package:cw_monero/api/types.dart';
|
|
|
|
import 'package:cw_monero/api/monero_api.dart';
|
|
|
|
import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
|
|
|
|
int _boolToInt(bool value) => value ? 1 : 0;
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
final getFileNameNative =
|
|
|
|
moneroApi.lookup<NativeFunction<get_filename>>('get_filename').asFunction<GetFilename>();
|
2023-07-24 20:23:09 +00:00
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
final getSeedNative = moneroApi.lookup<NativeFunction<get_seed>>('seed').asFunction<GetSeed>();
|
2023-07-24 20:23:09 +00:00
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
final getAddressNative =
|
|
|
|
moneroApi.lookup<NativeFunction<get_address>>('get_address').asFunction<GetAddress>();
|
2023-07-24 20:23:09 +00:00
|
|
|
|
|
|
|
final getFullBalanceNative = moneroApi
|
|
|
|
.lookup<NativeFunction<get_full_balanace>>('get_full_balance')
|
|
|
|
.asFunction<GetFullBalance>();
|
|
|
|
|
|
|
|
final getUnlockedBalanceNative = moneroApi
|
|
|
|
.lookup<NativeFunction<get_unlocked_balanace>>('get_unlocked_balance')
|
|
|
|
.asFunction<GetUnlockedBalance>();
|
|
|
|
|
|
|
|
final getCurrentHeightNative = moneroApi
|
|
|
|
.lookup<NativeFunction<get_current_height>>('get_current_height')
|
|
|
|
.asFunction<GetCurrentHeight>();
|
|
|
|
|
|
|
|
final getNodeHeightNative = moneroApi
|
|
|
|
.lookup<NativeFunction<get_node_height>>('get_node_height')
|
|
|
|
.asFunction<GetNodeHeight>();
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
final isConnectedNative =
|
|
|
|
moneroApi.lookup<NativeFunction<is_connected>>('is_connected').asFunction<IsConnected>();
|
2023-07-24 20:23:09 +00:00
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
final setupNodeNative =
|
|
|
|
moneroApi.lookup<NativeFunction<setup_node>>('setup_node').asFunction<SetupNode>();
|
2023-07-24 20:23:09 +00:00
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
final startRefreshNative =
|
|
|
|
moneroApi.lookup<NativeFunction<start_refresh>>('start_refresh').asFunction<StartRefresh>();
|
2023-07-24 20:23:09 +00:00
|
|
|
|
|
|
|
final connecToNodeNative = moneroApi
|
|
|
|
.lookup<NativeFunction<connect_to_node>>('connect_to_node')
|
|
|
|
.asFunction<ConnectToNode>();
|
|
|
|
|
|
|
|
final setRefreshFromBlockHeightNative = moneroApi
|
2023-08-02 17:16:43 +00:00
|
|
|
.lookup<NativeFunction<set_refresh_from_block_height>>('set_refresh_from_block_height')
|
2023-07-24 20:23:09 +00:00
|
|
|
.asFunction<SetRefreshFromBlockHeight>();
|
|
|
|
|
|
|
|
final setRecoveringFromSeedNative = moneroApi
|
2023-08-02 17:16:43 +00:00
|
|
|
.lookup<NativeFunction<set_recovering_from_seed>>('set_recovering_from_seed')
|
2023-07-24 20:23:09 +00:00
|
|
|
.asFunction<SetRecoveringFromSeed>();
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
final storeNative = moneroApi.lookup<NativeFunction<store_c>>('store').asFunction<Store>();
|
2023-07-24 20:23:09 +00:00
|
|
|
|
|
|
|
final setPasswordNative =
|
|
|
|
moneroApi.lookup<NativeFunction<set_password>>('set_password').asFunction<SetPassword>();
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
final setListenerNative =
|
|
|
|
moneroApi.lookup<NativeFunction<set_listener>>('set_listener').asFunction<SetListener>();
|
2023-07-24 20:23:09 +00:00
|
|
|
|
|
|
|
final getSyncingHeightNative = moneroApi
|
|
|
|
.lookup<NativeFunction<get_syncing_height>>('get_syncing_height')
|
|
|
|
.asFunction<GetSyncingHeight>();
|
|
|
|
|
|
|
|
final isNeededToRefreshNative = moneroApi
|
|
|
|
.lookup<NativeFunction<is_needed_to_refresh>>('is_needed_to_refresh')
|
|
|
|
.asFunction<IsNeededToRefresh>();
|
|
|
|
|
|
|
|
final isNewTransactionExistNative = moneroApi
|
2023-08-02 17:16:43 +00:00
|
|
|
.lookup<NativeFunction<is_new_transaction_exist>>('is_new_transaction_exist')
|
2023-07-24 20:23:09 +00:00
|
|
|
.asFunction<IsNewTransactionExist>();
|
|
|
|
|
|
|
|
final getSecretViewKeyNative = moneroApi
|
|
|
|
.lookup<NativeFunction<secret_view_key>>('secret_view_key')
|
|
|
|
.asFunction<SecretViewKey>();
|
|
|
|
|
|
|
|
final getPublicViewKeyNative = moneroApi
|
|
|
|
.lookup<NativeFunction<public_view_key>>('public_view_key')
|
|
|
|
.asFunction<PublicViewKey>();
|
|
|
|
|
|
|
|
final getSecretSpendKeyNative = moneroApi
|
|
|
|
.lookup<NativeFunction<secret_spend_key>>('secret_spend_key')
|
|
|
|
.asFunction<SecretSpendKey>();
|
|
|
|
|
|
|
|
final getPublicSpendKeyNative = moneroApi
|
|
|
|
.lookup<NativeFunction<secret_view_key>>('public_spend_key')
|
|
|
|
.asFunction<PublicSpendKey>();
|
|
|
|
|
|
|
|
final closeCurrentWalletNative = moneroApi
|
|
|
|
.lookup<NativeFunction<close_current_wallet>>('close_current_wallet')
|
|
|
|
.asFunction<CloseCurrentWallet>();
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
final onStartupNative =
|
|
|
|
moneroApi.lookup<NativeFunction<on_startup>>('on_startup').asFunction<OnStartup>();
|
2023-07-24 20:23:09 +00:00
|
|
|
|
|
|
|
final rescanBlockchainAsyncNative = moneroApi
|
|
|
|
.lookup<NativeFunction<rescan_blockchain>>('rescan_blockchain')
|
|
|
|
.asFunction<RescanBlockchainAsync>();
|
|
|
|
|
|
|
|
final getSubaddressLabelNative = moneroApi
|
|
|
|
.lookup<NativeFunction<get_subaddress_label>>('get_subaddress_label')
|
|
|
|
.asFunction<GetSubaddressLabel>();
|
|
|
|
|
|
|
|
final setTrustedDaemonNative = moneroApi
|
|
|
|
.lookup<NativeFunction<set_trusted_daemon>>('set_trusted_daemon')
|
|
|
|
.asFunction<SetTrustedDaemon>();
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
final trustedDaemonNative =
|
|
|
|
moneroApi.lookup<NativeFunction<trusted_daemon>>('trusted_daemon').asFunction<TrustedDaemon>();
|
2023-07-24 20:23:09 +00:00
|
|
|
|
|
|
|
int getSyncingHeight() => getSyncingHeightNative();
|
|
|
|
|
|
|
|
bool isNeededToRefresh() => isNeededToRefreshNative() != 0;
|
|
|
|
|
|
|
|
bool isNewTransactionExist() => isNewTransactionExistNative() != 0;
|
|
|
|
|
|
|
|
String getFilename() => convertUTF8ToString(pointer: getFileNameNative());
|
|
|
|
|
|
|
|
String getSeed() => convertUTF8ToString(pointer: getSeedNative());
|
|
|
|
|
|
|
|
String getAddress({int accountIndex = 0, int addressIndex = 0}) =>
|
|
|
|
convertUTF8ToString(pointer: getAddressNative(accountIndex, addressIndex));
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
int getFullBalance({int accountIndex = 0}) => getFullBalanceNative(accountIndex);
|
2023-07-24 20:23:09 +00:00
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
int getUnlockedBalance({int accountIndex = 0}) => getUnlockedBalanceNative(accountIndex);
|
2023-07-24 20:23:09 +00:00
|
|
|
|
|
|
|
int getCurrentHeight() => getCurrentHeightNative();
|
|
|
|
|
|
|
|
int getNodeHeightSync() => getNodeHeightNative();
|
|
|
|
|
|
|
|
bool isConnectedSync() => isConnectedNative() != 0;
|
|
|
|
|
|
|
|
bool setupNodeSync(
|
|
|
|
{required String address,
|
|
|
|
String? login,
|
|
|
|
String? password,
|
|
|
|
bool useSSL = false,
|
|
|
|
bool isLightWallet = false}) {
|
|
|
|
final addressPointer = address.toNativeUtf8();
|
|
|
|
Pointer<Utf8>? loginPointer;
|
|
|
|
Pointer<Utf8>? passwordPointer;
|
|
|
|
|
|
|
|
if (login != null) {
|
|
|
|
loginPointer = login.toNativeUtf8();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (password != null) {
|
|
|
|
passwordPointer = password.toNativeUtf8();
|
|
|
|
}
|
|
|
|
|
|
|
|
final errorMessagePointer = ''.toNativeUtf8();
|
2023-08-02 17:16:43 +00:00
|
|
|
// final isSetupNode = setupNodeNative(
|
|
|
|
// addressPointer,
|
|
|
|
// loginPointer,
|
|
|
|
// passwordPointer,
|
|
|
|
// _boolToInt(useSSL),
|
|
|
|
// _boolToInt(isLightWallet),
|
|
|
|
// errorMessagePointer) !=
|
|
|
|
// 0;
|
2023-07-24 20:23:09 +00:00
|
|
|
|
|
|
|
calloc.free(addressPointer);
|
|
|
|
|
|
|
|
if (loginPointer != null) {
|
|
|
|
calloc.free(loginPointer);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (passwordPointer != null) {
|
|
|
|
calloc.free(passwordPointer);
|
|
|
|
}
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
// if (!isSetupNode) {
|
|
|
|
// throw SetupWalletException(
|
|
|
|
// message: convertUTF8ToString(pointer: errorMessagePointer));
|
|
|
|
// }
|
2023-07-24 20:23:09 +00:00
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
// return isSetupNode;
|
|
|
|
return true;
|
2023-07-24 20:23:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void startRefreshSync() => startRefreshNative();
|
|
|
|
|
|
|
|
Future<bool> connectToNode() async => connecToNodeNative() != 0;
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
void setRefreshFromBlockHeight({required int height}) => setRefreshFromBlockHeightNative(height);
|
2023-07-24 20:23:09 +00:00
|
|
|
|
|
|
|
void setRecoveringFromSeed({required bool isRecovery}) =>
|
|
|
|
setRecoveringFromSeedNative(_boolToInt(isRecovery));
|
|
|
|
|
|
|
|
void storeSync() {
|
|
|
|
final pathPointer = ''.toNativeUtf8();
|
|
|
|
storeNative(pathPointer);
|
|
|
|
calloc.free(pathPointer);
|
|
|
|
}
|
|
|
|
|
|
|
|
void setPasswordSync(String password) {
|
|
|
|
final passwordPointer = password.toNativeUtf8();
|
|
|
|
final errorMessagePointer = calloc<Utf8Box>();
|
|
|
|
final changed = setPasswordNative(passwordPointer, errorMessagePointer) != 0;
|
|
|
|
calloc.free(passwordPointer);
|
2023-08-02 17:16:43 +00:00
|
|
|
|
2023-07-24 20:23:09 +00:00
|
|
|
if (!changed) {
|
|
|
|
final message = errorMessagePointer.ref.getValue();
|
|
|
|
calloc.free(errorMessagePointer);
|
|
|
|
throw Exception(message);
|
|
|
|
}
|
|
|
|
|
|
|
|
calloc.free(errorMessagePointer);
|
|
|
|
}
|
|
|
|
|
|
|
|
void closeCurrentWallet() => closeCurrentWalletNative();
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
String getSecretViewKey() => convertUTF8ToString(pointer: getSecretViewKeyNative());
|
2023-07-24 20:23:09 +00:00
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
String getPublicViewKey() => convertUTF8ToString(pointer: getPublicViewKeyNative());
|
2023-07-24 20:23:09 +00:00
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
String getSecretSpendKey() => convertUTF8ToString(pointer: getSecretSpendKeyNative());
|
2023-07-24 20:23:09 +00:00
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
String getPublicSpendKey() => convertUTF8ToString(pointer: getPublicSpendKeyNative());
|
2023-07-24 20:23:09 +00:00
|
|
|
|
|
|
|
class SyncListener {
|
2023-08-02 17:16:43 +00:00
|
|
|
SyncListener(this.onNewBlock, this.onNewTransaction)
|
|
|
|
: _cachedBlockchainHeight = 0,
|
|
|
|
_lastKnownBlockHeight = 0,
|
|
|
|
_initialSyncHeight = 0;
|
2023-07-24 20:23:09 +00:00
|
|
|
|
|
|
|
void Function(int, int, double) onNewBlock;
|
|
|
|
void Function() onNewTransaction;
|
|
|
|
|
|
|
|
Timer? _updateSyncInfoTimer;
|
|
|
|
int _cachedBlockchainHeight;
|
|
|
|
int _lastKnownBlockHeight;
|
|
|
|
int _initialSyncHeight;
|
|
|
|
|
|
|
|
Future<int> getNodeHeightOrUpdate(int baseHeight) async {
|
|
|
|
if (_cachedBlockchainHeight < baseHeight || _cachedBlockchainHeight == 0) {
|
|
|
|
_cachedBlockchainHeight = await getNodeHeight();
|
|
|
|
}
|
|
|
|
|
|
|
|
return _cachedBlockchainHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
void start() {
|
|
|
|
_cachedBlockchainHeight = 0;
|
|
|
|
_lastKnownBlockHeight = 0;
|
|
|
|
_initialSyncHeight = 0;
|
2023-08-02 17:16:43 +00:00
|
|
|
_updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: 1200), (_) async {
|
2023-07-24 20:23:09 +00:00
|
|
|
if (isNewTransactionExist()) {
|
|
|
|
onNewTransaction();
|
|
|
|
}
|
|
|
|
|
|
|
|
var syncHeight = getSyncingHeight();
|
|
|
|
|
|
|
|
if (syncHeight <= 0) {
|
|
|
|
syncHeight = getCurrentHeight();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_initialSyncHeight <= 0) {
|
|
|
|
_initialSyncHeight = syncHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
final bchHeight = await getNodeHeightOrUpdate(syncHeight);
|
|
|
|
|
|
|
|
if (_lastKnownBlockHeight == syncHeight || syncHeight == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_lastKnownBlockHeight = syncHeight;
|
|
|
|
final track = bchHeight - _initialSyncHeight;
|
|
|
|
final diff = track - (bchHeight - syncHeight);
|
|
|
|
final ptc = diff <= 0 ? 0.0 : diff / track;
|
|
|
|
final left = bchHeight - syncHeight;
|
|
|
|
|
|
|
|
if (syncHeight < 0 || left < 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
|
|
|
|
onNewBlock?.call(syncHeight, left, ptc);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void stop() => _updateSyncInfoTimer?.cancel();
|
|
|
|
}
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
SyncListener setListeners(
|
|
|
|
void Function(int, int, double) onNewBlock, void Function() onNewTransaction) {
|
2023-07-24 20:23:09 +00:00
|
|
|
final listener = SyncListener(onNewBlock, onNewTransaction);
|
|
|
|
setListenerNative();
|
|
|
|
return listener;
|
|
|
|
}
|
|
|
|
|
|
|
|
void onStartup() => onStartupNative();
|
|
|
|
|
|
|
|
void _storeSync(Object _) => storeSync();
|
|
|
|
|
|
|
|
bool _setupNodeSync(Map<String, Object?> args) {
|
|
|
|
final address = args['address'] as String;
|
|
|
|
final login = (args['login'] ?? '') as String;
|
|
|
|
final password = (args['password'] ?? '') as String;
|
|
|
|
final useSSL = args['useSSL'] as bool;
|
|
|
|
final isLightWallet = args['isLightWallet'] as bool;
|
|
|
|
|
|
|
|
return setupNodeSync(
|
|
|
|
address: address,
|
|
|
|
login: login,
|
|
|
|
password: password,
|
|
|
|
useSSL: useSSL,
|
|
|
|
isLightWallet: isLightWallet);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool _isConnected(Object _) => isConnectedSync();
|
|
|
|
|
|
|
|
int _getNodeHeight(Object _) => getNodeHeightSync();
|
|
|
|
|
|
|
|
void startRefresh() => startRefreshSync();
|
|
|
|
|
|
|
|
Future<void> setupNode(
|
|
|
|
{required String address,
|
|
|
|
String? login,
|
|
|
|
String? password,
|
|
|
|
bool useSSL = false,
|
|
|
|
bool isLightWallet = false}) =>
|
|
|
|
compute<Map<String, Object?>, void>(_setupNodeSync, {
|
|
|
|
'address': address,
|
2023-08-02 17:16:43 +00:00
|
|
|
'login': login,
|
2023-07-24 20:23:09 +00:00
|
|
|
'password': password,
|
|
|
|
'useSSL': useSSL,
|
|
|
|
'isLightWallet': isLightWallet
|
|
|
|
});
|
|
|
|
|
|
|
|
Future<void> store() => compute<int, void>(_storeSync, 0);
|
|
|
|
|
|
|
|
Future<bool> isConnected() => compute(_isConnected, 0);
|
|
|
|
|
|
|
|
Future<int> getNodeHeight() => compute(_getNodeHeight, 0);
|
|
|
|
|
|
|
|
void rescanBlockchainAsync() => rescanBlockchainAsyncNative();
|
|
|
|
|
|
|
|
String getSubaddressLabel(int accountIndex, int addressIndex) {
|
|
|
|
return convertUTF8ToString(pointer: getSubaddressLabelNative(accountIndex, addressIndex));
|
|
|
|
}
|
|
|
|
|
|
|
|
Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted));
|
|
|
|
|
2023-08-02 17:16:43 +00:00
|
|
|
Future<bool> trustedDaemon() async => trustedDaemonNative() != 0;
|