Files
peyawallet/lib/native/wallet_ffi_backend.dart
Codex Bot 6480e4a555
build / Build Linux wallet (push) Successful in 2m19s
build / Build Windows wallet (push) Successful in 20m35s
Improve wallet password handling and seed backup flow
2026-04-17 18:41:18 +02:00

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;
}
}
}