330 lines
9.2 KiB
Dart
330 lines
9.2 KiB
Dart
import 'dart:ffi' as ffi;
|
|
|
|
import 'package:ffi/ffi.dart';
|
|
import 'package:logger/logger.dart';
|
|
|
|
import '../domain/models.dart';
|
|
import '../domain/send.dart';
|
|
import '../domain/staking.dart';
|
|
import '../domain/transactions.dart';
|
|
import 'native_library_loader.dart';
|
|
import 'wallet_backend.dart';
|
|
import 'wallet_wrapper_bindings.dart';
|
|
|
|
class WalletFfiBackend implements WalletBackend {
|
|
WalletFfiBackend(this._logger)
|
|
: _bindings = WalletWrapperBindings(
|
|
NativeLibraryLoader(logger: _logger).load(),
|
|
);
|
|
|
|
final Logger _logger;
|
|
final WalletWrapperBindings _bindings;
|
|
|
|
@override
|
|
int get maxDestinations => 1;
|
|
|
|
@override
|
|
Future<void> createWallet({
|
|
required String name,
|
|
required String path,
|
|
required String password,
|
|
}) async {
|
|
await _guarded('createWallet', () {
|
|
// TODO: Map to monero_c Salvium create wallet API via native wrapper.
|
|
final namePtr = name.toNativeUtf8();
|
|
final pathPtr = path.toNativeUtf8();
|
|
final passPtr = password.toNativeUtf8();
|
|
try {
|
|
_ensureOk(
|
|
_bindings.walletCreate(
|
|
namePtr.cast(), pathPtr.cast(), passPtr.cast()),
|
|
'create wallet');
|
|
} finally {
|
|
calloc.free(namePtr);
|
|
calloc.free(pathPtr);
|
|
calloc.free(passPtr);
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<void> restoreWalletFromSeed({
|
|
required String name,
|
|
required String seed,
|
|
required String path,
|
|
required String password,
|
|
}) async {
|
|
await _guarded('restoreWalletFromSeed', () {
|
|
// TODO: Map to monero_c Salvium restore-from-seed API via native wrapper.
|
|
final namePtr = name.toNativeUtf8();
|
|
final seedPtr = seed.toNativeUtf8();
|
|
final pathPtr = path.toNativeUtf8();
|
|
final passPtr = password.toNativeUtf8();
|
|
try {
|
|
_ensureOk(
|
|
_bindings.walletRestoreFromSeed(
|
|
namePtr.cast(),
|
|
seedPtr.cast(),
|
|
pathPtr.cast(),
|
|
passPtr.cast(),
|
|
),
|
|
'restore wallet',
|
|
);
|
|
} finally {
|
|
calloc.free(namePtr);
|
|
calloc.free(seedPtr);
|
|
calloc.free(pathPtr);
|
|
calloc.free(passPtr);
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<void> openWallet(
|
|
{required String path, required String password}) async {
|
|
await _guarded('openWallet', () {
|
|
// TODO: Map to monero_c Salvium open wallet API via native wrapper.
|
|
final pathPtr = path.toNativeUtf8();
|
|
final passPtr = password.toNativeUtf8();
|
|
try {
|
|
_ensureOk(_bindings.walletOpen(pathPtr.cast(), passPtr.cast()),
|
|
'open wallet');
|
|
} finally {
|
|
calloc.free(pathPtr);
|
|
calloc.free(passPtr);
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<bool> verifyWalletPassword({
|
|
required String path,
|
|
required String password,
|
|
}) async {
|
|
return _guarded<bool>('verifyWalletPassword', () {
|
|
throw UnsupportedError(
|
|
'Password verification not supported by native wrapper backend',
|
|
);
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<void> closeWallet() async {
|
|
await _guarded('closeWallet', () {
|
|
_bindings.walletClose();
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<String> getAddress() async {
|
|
return _guarded<String>('getAddress', () {
|
|
// TODO: Map to monero_c Salvium get address API via native wrapper.
|
|
final ptr = _bindings.walletGetAddress();
|
|
if (ptr == ffi.nullptr) {
|
|
return '';
|
|
}
|
|
final value = ptr.cast<Utf8>().toDartString();
|
|
_bindings.walletFreeString(ptr);
|
|
return value;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<bool> isConnected() async {
|
|
return _guarded<bool>('isConnected', () => false);
|
|
}
|
|
|
|
@override
|
|
Future<String> createSubaddress({String? label}) async {
|
|
return _guarded<String>('createSubaddress', () {
|
|
// TODO: Map to monero_c Salvium subaddress API via native wrapper.
|
|
throw StateError('Subaddress not supported by native wrapper');
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<List<SubaddressInfo>> getSubaddresses() async {
|
|
return _guarded<List<SubaddressInfo>>('getSubaddresses', () {
|
|
// TODO: Map to monero_c Salvium subaddress list API via native wrapper.
|
|
return const [];
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<void> setSubaddressLabel({
|
|
required int accountIndex,
|
|
required int addressIndex,
|
|
required String label,
|
|
}) async {
|
|
return _guarded<void>('setSubaddressLabel', () {
|
|
// TODO: Map to monero_c Salvium subaddress label API via native wrapper.
|
|
throw StateError('Subaddress label not supported by native wrapper');
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<int> getBalance() async {
|
|
// TODO: Map to monero_c Salvium get balance API via native wrapper.
|
|
return _guarded<int>('getBalance', () => _bindings.walletGetBalance());
|
|
}
|
|
|
|
@override
|
|
Future<int> getUnlockedBalance() async {
|
|
// TODO: Map to monero_c Salvium unlocked balance API via native wrapper.
|
|
return _guarded<int>(
|
|
'getUnlockedBalance', () => _bindings.walletGetBalance());
|
|
}
|
|
|
|
@override
|
|
Future<String> getSeed() async {
|
|
// TODO: Map to monero_c Salvium seed API via native wrapper.
|
|
return _guarded<String>('getSeed', () {
|
|
throw StateError('Seed not supported by native wrapper');
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<List<WalletTransaction>> getTransactions() async {
|
|
// TODO: Map to monero_c Salvium transaction history API via native wrapper.
|
|
return const [];
|
|
}
|
|
|
|
@override
|
|
Future<void> connectToNode(NodeConfig nodeConfig) async {
|
|
await _guarded('connectToNode', () {
|
|
// TODO: Map to monero_c Salvium node connect APIs via native wrapper.
|
|
if (nodeConfig.mode == NodeMode.local) {
|
|
_ensureOk(_bindings.walletConnectLocal(), 'connect local node');
|
|
return;
|
|
}
|
|
final remote = nodeConfig.activeRemote;
|
|
if (remote == null) {
|
|
throw StateError('No remote node selected');
|
|
}
|
|
final hostPtr = remote.host.toNativeUtf8();
|
|
try {
|
|
_ensureOk(
|
|
_bindings.walletConnectRemote(
|
|
hostPtr.cast(),
|
|
remote.port,
|
|
remote.trusted ? 1 : 0,
|
|
),
|
|
'connect remote node',
|
|
);
|
|
} finally {
|
|
calloc.free(hostPtr);
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<int> getNodeHeight() async {
|
|
// TODO: Map to monero_c Salvium node height API via native wrapper.
|
|
return _guarded<int>(
|
|
'getNodeHeight', () => _bindings.walletGetNodeHeight());
|
|
}
|
|
|
|
@override
|
|
Future<int> getWalletHeight() async {
|
|
// TODO: Map to monero_c Salvium wallet height API via native wrapper.
|
|
return _guarded<int>(
|
|
'getWalletHeight', () => _bindings.walletGetWalletHeight());
|
|
}
|
|
|
|
@override
|
|
Future<bool> isSynced() async {
|
|
// TODO: Map to monero_c Salvium sync status API via native wrapper.
|
|
return _guarded<bool>('isSynced', () => _bindings.walletIsSynced() == 1);
|
|
}
|
|
|
|
@override
|
|
Future<double> getSyncProgress() async {
|
|
// TODO: Map to monero_c Salvium sync progress API via native wrapper.
|
|
return _guarded<double>(
|
|
'getSyncProgress', () => _bindings.walletSyncProgress());
|
|
}
|
|
|
|
@override
|
|
Future<void> refresh() async {
|
|
await _guarded('refresh', () {
|
|
// TODO: Map to monero_c Salvium refresh/sync API via native wrapper.
|
|
_ensureOk(_bindings.walletRefresh(), 'refresh wallet');
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<void> rescanBlockchain({int? fromHeight}) async {
|
|
return _guarded('rescanBlockchain', () {
|
|
throw UnsupportedError('Rescan not supported by native wrapper backend');
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<SendPreview> prepareSend(SendRequest request) async {
|
|
return _guarded<SendPreview>('prepareSend', () {
|
|
throw UnsupportedError('Send not supported by native wrapper backend');
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<SendResult> commitPreparedSend() async {
|
|
return _guarded<SendResult>('commitPreparedSend', () {
|
|
throw UnsupportedError('Send not supported by native wrapper backend');
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<void> discardPreparedSend() async {
|
|
await _guarded('discardPreparedSend', () {
|
|
throw UnsupportedError('Send not supported by native wrapper backend');
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<StakePreview> prepareStake({
|
|
required int amountAtomic,
|
|
int accountIndex = 0,
|
|
}) async {
|
|
return _guarded<StakePreview>('prepareStake', () {
|
|
throw UnsupportedError('Stake not supported by native wrapper backend');
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<Map<String, StakeYieldInfo>> getActiveStakeYields({
|
|
required List<WalletTransaction> activeStakes,
|
|
required int currentHeight,
|
|
}) async {
|
|
return const {};
|
|
}
|
|
|
|
@override
|
|
Future<StakeResult> commitPreparedStake() async {
|
|
return _guarded<StakeResult>('commitPreparedStake', () {
|
|
throw UnsupportedError('Stake not supported by native wrapper backend');
|
|
});
|
|
}
|
|
|
|
@override
|
|
Future<void> discardPreparedStake() async {
|
|
await _guarded('discardPreparedStake', () {
|
|
throw UnsupportedError('Stake not supported by native wrapper backend');
|
|
});
|
|
}
|
|
|
|
void _ensureOk(int code, String action) {
|
|
if (code != 0) {
|
|
throw StateError('Wallet backend error during $action (code=$code)');
|
|
}
|
|
}
|
|
|
|
Future<T> _guarded<T>(String label, T Function() fn) async {
|
|
try {
|
|
return await Future<T>(() => fn());
|
|
} catch (error, stack) {
|
|
_logger.e('FFI $label failed', error: error, stackTrace: stack);
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|