diff --git a/flutter_rust_bridge.yaml b/flutter_rust_bridge.yaml index edb2e0a..e3b17dd 100644 --- a/flutter_rust_bridge.yaml +++ b/flutter_rust_bridge.yaml @@ -1,2 +1,2 @@ rust_input: rust/src/api/**/*.rs -dart_output: lib/rust \ No newline at end of file +dart_output: lib/generated/rust \ No newline at end of file diff --git a/lib/rust/api/simple.dart b/lib/generated/rust/api/simple.dart similarity index 98% rename from lib/rust/api/simple.dart rename to lib/generated/rust/api/simple.dart index 3b45074..2bd3dae 100644 --- a/lib/rust/api/simple.dart +++ b/lib/generated/rust/api/simple.dart @@ -17,9 +17,6 @@ Stream createLogStream( RustLib.instance.api.crateApiSimpleCreateLogStream( level: level, logDependencies: logDependencies); -Stream createSyncStream() => - RustLib.instance.api.crateApiSimpleCreateSyncStream(); - Stream createScanProgressStream() => RustLib.instance.api.crateApiSimpleCreateScanProgressStream(); @@ -51,7 +48,7 @@ String changeBirthday({required String encodedWallet, required int birthday}) => String resetWallet({required String encodedWallet}) => RustLib.instance.api .crateApiSimpleResetWallet(encodedWallet: encodedWallet); -Future syncBlockchain() => +Future syncBlockchain() => RustLib.instance.api.crateApiSimpleSyncBlockchain(); Future scanToTip({required String encodedWallet}) => diff --git a/lib/rust/api/simple.freezed.dart b/lib/generated/rust/api/simple.freezed.dart similarity index 100% rename from lib/rust/api/simple.freezed.dart rename to lib/generated/rust/api/simple.freezed.dart diff --git a/lib/rust/frb_generated.dart b/lib/generated/rust/frb_generated.dart similarity index 93% rename from lib/rust/frb_generated.dart rename to lib/generated/rust/frb_generated.dart index 0218bea..e54e1ad 100644 --- a/lib/rust/frb_generated.dart +++ b/lib/generated/rust/frb_generated.dart @@ -58,7 +58,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.0.0-dev.37'; @override - int get rustContentHash => -1193702642; + int get rustContentHash => -980625945; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -89,8 +89,6 @@ abstract class RustLibApi extends BaseApi { Stream crateApiSimpleCreateScanProgressStream(); - Stream crateApiSimpleCreateSyncStream(); - String crateApiSimpleExtractTxFromPsbt({required String psbt}); String crateApiSimpleFillSpOutputs( @@ -124,7 +122,7 @@ abstract class RustLibApi extends BaseApi { required String psbt, required bool finalize}); - Future crateApiSimpleSyncBlockchain(); + Future crateApiSimpleSyncBlockchain(); } class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @@ -321,39 +319,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["s"], ); - @override - Stream crateApiSimpleCreateSyncStream() { - final s = RustStreamSink(); - handler.executeSync(SyncTask( - callFfi: () { - final serializer = SseSerializer(generalizedFrbRustBinding); - sse_encode_StreamSink_sync_status_Sse(s, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 8)!; - }, - codec: SseCodec( - decodeSuccessData: sse_decode_unit, - decodeErrorData: null, - ), - constMeta: kCrateApiSimpleCreateSyncStreamConstMeta, - argValues: [s], - apiImpl: this, - )); - return s.stream; - } - - TaskConstMeta get kCrateApiSimpleCreateSyncStreamConstMeta => - const TaskConstMeta( - debugName: "create_sync_stream", - argNames: ["s"], - ); - @override String crateApiSimpleExtractTxFromPsbt({required String psbt}) { return handler.executeSync(SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(psbt, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 9)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 8)!; }, codec: SseCodec( decodeSuccessData: sse_decode_String, @@ -379,7 +351,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(encodedWallet, serializer); sse_encode_String(psbt, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 10)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 9)!; }, codec: SseCodec( decodeSuccessData: sse_decode_String, @@ -403,7 +375,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(encodedWallet, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 11)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 10)!; }, codec: SseCodec( decodeSuccessData: sse_decode_wallet_status, @@ -427,7 +399,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 12, port: port_); + funcId: 11, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_unit, @@ -455,7 +427,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_String(encodedWallet, serializer); sse_encode_String(spentBy, serializer); sse_encode_list_String(spent, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 13)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 12)!; }, codec: SseCodec( decodeSuccessData: sse_decode_String, @@ -479,7 +451,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(encodedWallet, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 14)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 13)!; }, codec: SseCodec( decodeSuccessData: sse_decode_String, @@ -503,7 +475,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(encodedWallet, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 15, port: port_); + funcId: 14, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_String, @@ -538,7 +510,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_u_32(birthday, serializer); sse_encode_String(network, serializer); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 16, port: port_); + funcId: 15, port: port_); }, codec: SseCodec( decodeSuccessData: sse_decode_String, @@ -568,7 +540,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(encodedWallet, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 17)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 16)!; }, codec: SseCodec( decodeSuccessData: sse_decode_opt_String, @@ -596,7 +568,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_String(encodedWallet, serializer); sse_encode_String(psbt, serializer); sse_encode_bool(finalize, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 18)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 17)!; }, codec: SseCodec( decodeSuccessData: sse_decode_String, @@ -614,15 +586,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); @override - Future crateApiSimpleSyncBlockchain() { + Future crateApiSimpleSyncBlockchain() { return handler.executeNormal(NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); pdeCallFfi(generalizedFrbRustBinding, serializer, - funcId: 19, port: port_); + funcId: 18, port: port_); }, codec: SseCodec( - decodeSuccessData: sse_decode_unit, + decodeSuccessData: sse_decode_u_32, decodeErrorData: sse_decode_AnyhowException, ), constMeta: kCrateApiSimpleSyncBlockchainConstMeta, @@ -663,13 +635,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { throw UnimplementedError(); } - @protected - RustStreamSink dco_decode_StreamSink_sync_status_Sse( - dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - throw UnimplementedError(); - } - @protected RustStreamSink dco_decode_StreamSink_u_64_Sse(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -838,17 +803,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); } - @protected - SyncStatus dco_decode_sync_status(dynamic raw) { - // Codec=Dco (DartCObject based), see doc to use other codecs - final arr = raw as List; - if (arr.length != 1) - throw Exception('unexpected arr length: expect 1 but see ${arr.length}'); - return SyncStatus( - blockheight: dco_decode_u_32(arr[0]), - ); - } - @protected int dco_decode_u_32(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -917,13 +871,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { throw UnimplementedError('Unreachable ()'); } - @protected - RustStreamSink sse_decode_StreamSink_sync_status_Sse( - SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - throw UnimplementedError('Unreachable ()'); - } - @protected RustStreamSink sse_decode_StreamSink_u_64_Sse( SseDeserializer deserializer) { @@ -1105,13 +1052,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return ScanProgress(start: var_start, current: var_current, end: var_end); } - @protected - SyncStatus sse_decode_sync_status(SseDeserializer deserializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - var var_blockheight = sse_decode_u_32(deserializer); - return SyncStatus(blockheight: var_blockheight); - } - @protected int sse_decode_u_32(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -1190,18 +1130,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer); } - @protected - void sse_encode_StreamSink_sync_status_Sse( - RustStreamSink self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_String( - self.setupAndSerialize( - codec: SseCodec( - decodeSuccessData: sse_decode_sync_status, - decodeErrorData: null)), - serializer); - } - @protected void sse_encode_StreamSink_u_64_Sse( RustStreamSink self, SseSerializer serializer) { @@ -1356,12 +1284,6 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_u_32(self.end, serializer); } - @protected - void sse_encode_sync_status(SyncStatus self, SseSerializer serializer) { - // Codec=Sse (Serialization based), see doc to use other codecs - sse_encode_u_32(self.blockheight, serializer); - } - @protected void sse_encode_u_32(int self, SseSerializer serializer) { // Codec=Sse (Serialization based), see doc to use other codecs diff --git a/lib/rust/frb_generated.io.dart b/lib/generated/rust/frb_generated.io.dart similarity index 93% rename from lib/rust/frb_generated.io.dart rename to lib/generated/rust/frb_generated.io.dart index 177971a..58e8490 100644 --- a/lib/rust/frb_generated.io.dart +++ b/lib/generated/rust/frb_generated.io.dart @@ -33,9 +33,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { RustStreamSink dco_decode_StreamSink_scan_progress_Sse( dynamic raw); - @protected - RustStreamSink dco_decode_StreamSink_sync_status_Sse(dynamic raw); - @protected RustStreamSink dco_decode_StreamSink_u_64_Sse(dynamic raw); @@ -91,9 +88,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ScanProgress dco_decode_scan_progress(dynamic raw); - @protected - SyncStatus dco_decode_sync_status(dynamic raw); - @protected int dco_decode_u_32(dynamic raw); @@ -124,10 +118,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { RustStreamSink sse_decode_StreamSink_scan_progress_Sse( SseDeserializer deserializer); - @protected - RustStreamSink sse_decode_StreamSink_sync_status_Sse( - SseDeserializer deserializer); - @protected RustStreamSink sse_decode_StreamSink_u_64_Sse( SseDeserializer deserializer); @@ -186,9 +176,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ScanProgress sse_decode_scan_progress(SseDeserializer deserializer); - @protected - SyncStatus sse_decode_sync_status(SseDeserializer deserializer); - @protected int sse_decode_u_32(SseDeserializer deserializer); @@ -220,10 +207,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { void sse_encode_StreamSink_scan_progress_Sse( RustStreamSink self, SseSerializer serializer); - @protected - void sse_encode_StreamSink_sync_status_Sse( - RustStreamSink self, SseSerializer serializer); - @protected void sse_encode_StreamSink_u_64_Sse( RustStreamSink self, SseSerializer serializer); @@ -284,9 +267,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_scan_progress(ScanProgress self, SseSerializer serializer); - @protected - void sse_encode_sync_status(SyncStatus self, SseSerializer serializer); - @protected void sse_encode_u_32(int self, SseSerializer serializer); diff --git a/lib/rust/frb_generated.web.dart b/lib/generated/rust/frb_generated.web.dart similarity index 93% rename from lib/rust/frb_generated.web.dart rename to lib/generated/rust/frb_generated.web.dart index 4a05137..4f39d73 100644 --- a/lib/rust/frb_generated.web.dart +++ b/lib/generated/rust/frb_generated.web.dart @@ -35,9 +35,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { RustStreamSink dco_decode_StreamSink_scan_progress_Sse( dynamic raw); - @protected - RustStreamSink dco_decode_StreamSink_sync_status_Sse(dynamic raw); - @protected RustStreamSink dco_decode_StreamSink_u_64_Sse(dynamic raw); @@ -93,9 +90,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ScanProgress dco_decode_scan_progress(dynamic raw); - @protected - SyncStatus dco_decode_sync_status(dynamic raw); - @protected int dco_decode_u_32(dynamic raw); @@ -126,10 +120,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { RustStreamSink sse_decode_StreamSink_scan_progress_Sse( SseDeserializer deserializer); - @protected - RustStreamSink sse_decode_StreamSink_sync_status_Sse( - SseDeserializer deserializer); - @protected RustStreamSink sse_decode_StreamSink_u_64_Sse( SseDeserializer deserializer); @@ -188,9 +178,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected ScanProgress sse_decode_scan_progress(SseDeserializer deserializer); - @protected - SyncStatus sse_decode_sync_status(SseDeserializer deserializer); - @protected int sse_decode_u_32(SseDeserializer deserializer); @@ -222,10 +209,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { void sse_encode_StreamSink_scan_progress_Sse( RustStreamSink self, SseSerializer serializer); - @protected - void sse_encode_StreamSink_sync_status_Sse( - RustStreamSink self, SseSerializer serializer); - @protected void sse_encode_StreamSink_u_64_Sse( RustStreamSink self, SseSerializer serializer); @@ -286,9 +269,6 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected void sse_encode_scan_progress(ScanProgress self, SseSerializer serializer); - @protected - void sse_encode_sync_status(SyncStatus self, SseSerializer serializer); - @protected void sse_encode_u_32(int self, SseSerializer serializer); diff --git a/lib/rust/logger.dart b/lib/generated/rust/logger.dart similarity index 100% rename from lib/rust/logger.dart rename to lib/generated/rust/logger.dart diff --git a/lib/rust/stream.dart b/lib/generated/rust/stream.dart similarity index 69% rename from lib/rust/stream.dart rename to lib/generated/rust/stream.dart index 1a03b3a..71c48a4 100644 --- a/lib/rust/stream.dart +++ b/lib/generated/rust/stream.dart @@ -29,21 +29,3 @@ class ScanProgress { current == other.current && end == other.end; } - -class SyncStatus { - final int blockheight; - - const SyncStatus({ - required this.blockheight, - }); - - @override - int get hashCode => blockheight.hashCode; - - @override - bool operator ==(Object other) => - identical(this, other) || - other is SyncStatus && - runtimeType == other.runtimeType && - blockheight == other.blockheight; -} diff --git a/lib/home.dart b/lib/home.dart deleted file mode 100644 index 65471e5..0000000 --- a/lib/home.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'dart:async'; - -import 'package:bitcoin_ui/bitcoin_ui.dart'; -import 'package:donationwallet/load_wallet.dart'; -import 'package:donationwallet/main.dart'; -import 'package:donationwallet/wallet.dart'; -import 'package:donationwallet/settings.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class HomeScreen extends StatefulWidget { - const HomeScreen({super.key}); - - @override - HomeScreenState createState() => HomeScreenState(); -} - -class HomeScreenState extends State { - bool isLoading = true; - int _selectedIndex = 0; - final List _widgetOptions = [ - const WalletScreen(), - const SettingsScreen(), - ]; - - @override - void initState() { - super.initState(); - _checkWallet(); - } - - Future _checkWallet() async { - final walletState = Provider.of(context, listen: false); - try { - await walletState.updateWalletStatus(); - walletState.walletLoaded = true; - } catch (e) { - walletState.walletLoaded = false; - } - setState(() { - isLoading = false; - }); - } - - void _onItemTapped(int index) { - setState(() { - _selectedIndex = index; - }); - } - - @override - Widget build(BuildContext context) { - if (isLoading) { - return const MaterialApp( - home: Scaffold( - body: Center(child: CircularProgressIndicator()), - ), - ); - } - - final walletState = Provider.of(context); - if (!walletState.walletLoaded) { - return Scaffold( - appBar: AppBar( - title: const Text('Wallet creation/restoration'), - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - ), - body: const LoadWalletScreen(), - ); - } else { - return Scaffold( - appBar: AppBar( - title: const Text('Silent Payments'), - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - ), - body: IndexedStack( - index: _selectedIndex, - children: _widgetOptions, - ), - bottomNavigationBar: BottomNavigationBar( - items: [ - BottomNavigationBarItem( - icon: Image( - image: const AssetImage("icons/wallet.png", - package: "bitcoin_ui"), - color: Bitcoin.neutral3Dark), - label: 'Wallet', - ), - BottomNavigationBarItem( - icon: Image( - image: - const AssetImage("icons/gear.png", package: "bitcoin_ui"), - color: Bitcoin.neutral3Dark), - label: 'Settings', - ), - ], - currentIndex: _selectedIndex, - selectedItemColor: Colors.green, - onTap: _onItemTapped, - ), - ); - } - } -} diff --git a/lib/load_wallet.dart b/lib/load_wallet.dart deleted file mode 100644 index 64ee70d..0000000 --- a/lib/load_wallet.dart +++ /dev/null @@ -1,341 +0,0 @@ -import 'dart:async'; - -import 'package:donationwallet/rust/api/simple.dart'; -import 'package:donationwallet/home.dart'; -import 'package:donationwallet/main.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; - -class LoadWalletScreen extends StatelessWidget { - const LoadWalletScreen({super.key}); - - Future _setup(BuildContext context, String? mnemonic, String? scanKey, - String? spendKey, int birthday) async { - final walletState = Provider.of(context, listen: false); - try { - await walletState.updateWalletStatus(); - walletState.walletLoaded = true; - return; - } catch (e) { - print("Creating a new wallet"); - } - - try { - final wallet = await setup( - label: walletState.label, - mnemonic: mnemonic, - scanKey: scanKey, - spendKey: spendKey, - birthday: birthday, - network: walletState.network, - ); - await walletState.saveWalletToSecureStorage(wallet); - await walletState.updateWalletStatus(); - walletState.walletLoaded = true; - } catch (e) { - rethrow; - } - } - - Future _showKeysInputDialog(BuildContext context, bool watchOnly, - Function(Exception? e) onSetupComplete) async { - TextEditingController scanKeyController = TextEditingController(); - TextEditingController spendKeyController = TextEditingController(); - TextEditingController birthdayController = TextEditingController(); - String hint; - - if (watchOnly) { - hint = "Spend public key"; - } else { - hint = "Spend private key"; - } - - await showDialog( - context: context, - builder: (BuildContext dialogContext) { - return AlertDialog( - title: const Text("Enter keys"), - content: Column( - mainAxisSize: MainAxisSize.min, // Use min size for the column - children: [ - TextField( - controller: scanKeyController, - decoration: InputDecoration( - hintText: "Scan private key", - suffixIcon: IconButton( - icon: const Icon(Icons.paste), - onPressed: () async { - ClipboardData? data = - await Clipboard.getData(Clipboard.kTextPlain); - if (data != null) { - scanKeyController.text = data.text ?? ''; - } - }, - ), - ), - ), - const SizedBox(height: 10), // Spacing between text fields - TextField( - controller: spendKeyController, - decoration: InputDecoration( - hintText: hint, - suffixIcon: IconButton( - icon: const Icon(Icons.paste), - onPressed: () async { - ClipboardData? data = - await Clipboard.getData(Clipboard.kTextPlain); - if (data != null) { - spendKeyController.text = data.text ?? ''; - } - }, - ), - ), - ), - const SizedBox(height: 10), // Spacing between text fields - TextField( - controller: birthdayController, - keyboardType: TextInputType.number, - decoration: InputDecoration( - hintText: "Wallet birthday (in blocks)", - suffixIcon: IconButton( - icon: const Icon(Icons.paste), - onPressed: () async { - ClipboardData? data = - await Clipboard.getData(Clipboard.kTextPlain); - if (data != null) { - birthdayController.text = data.text ?? ''; - } - }, - ), - ), - ), - ], - ), - actions: [ - TextButton( - child: const Text("Cancel"), - onPressed: () { - Navigator.of(dialogContext).pop(); // Close the dialog - }, - ), - TextButton( - child: const Text("Submit"), - onPressed: () async { - Navigator.of(dialogContext).pop(); // Close the dialog - // Process the input from the two text fields - final scanKey = scanKeyController.text; - final spendKey = spendKeyController.text; - final birthday = int.parse(birthdayController.text); - - if (scanKey.isEmpty || spendKey.isEmpty) { - throw Error(); - } - - try { - await _setup(context, null, scanKey, spendKey, birthday); - onSetupComplete(null); - } on Exception catch (e) { - onSetupComplete(e); - } catch (e) { - rethrow; - } - }, - ), - ], - ); - }, - ); - } - - Future _showSeedInputDialog( - BuildContext context, Function(Exception?) onSetupComplete) async { - TextEditingController seedController = TextEditingController(); - TextEditingController birthdayController = TextEditingController(); - - await showDialog( - context: context, - builder: (BuildContext dialogContext) { - return AlertDialog( - title: const Text("Enter Seed"), - content: Column( - mainAxisSize: MainAxisSize.min, // Use min size for the column - children: [ - TextField( - controller: seedController, - decoration: InputDecoration( - hintText: "Seed", - suffixIcon: IconButton( - icon: const Icon(Icons.paste), - onPressed: () async { - ClipboardData? data = - await Clipboard.getData(Clipboard.kTextPlain); - if (data != null) { - seedController.text = data.text ?? ''; - } - }, - ), - ), - ), - const SizedBox(height: 10), // Spacing between text fields - TextField( - controller: birthdayController, - keyboardType: TextInputType.number, - decoration: InputDecoration( - hintText: "Wallet birthday (in blocks)", - suffixIcon: IconButton( - icon: const Icon(Icons.paste), - onPressed: () async { - ClipboardData? data = - await Clipboard.getData(Clipboard.kTextPlain); - if (data != null) { - birthdayController.text = data.text ?? ''; - } - }, - ), - ), - ), - ], - ), - actions: [ - TextButton( - child: const Text("Cancel"), - onPressed: () { - Navigator.of(dialogContext).pop(); // Close the dialog - }, - ), - TextButton( - child: const Text("Submit"), - onPressed: () async { - Navigator.of(dialogContext).pop(); // Close the dialog - // Process the input from the two text fields - final mnemonic = seedController.text; - final birthday = int.parse(birthdayController.text); - try { - await _setup(context, mnemonic, null, null, birthday); - onSetupComplete(null); - } on Exception catch (e) { - onSetupComplete(e); - } catch (e) { - rethrow; - } - }, - ), - ], - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Spacer(), - Expanded( - child: _buildButton( - context, - 'Create New Wallet', - () async { - final navigator = Navigator.of(context); - final walletState = - Provider.of(context, listen: false); - try { - await _setup(context, null, null, null, walletState.tip); - } catch (e) { - rethrow; - } - if (walletState.walletLoaded) { - navigator.pushReplacement(MaterialPageRoute( - builder: (context) => const HomeScreen())); - } - }, - ), - ), - Expanded( - child: _buildButton( - context, - 'Restore from seed', - () async { - final navigator = Navigator.of(context); - final walletState = - Provider.of(context, listen: false); - await _showSeedInputDialog(context, (Exception? e) async { - if (e != null) { - throw e; - } else if (walletState.walletLoaded) { - navigator.pushReplacement(MaterialPageRoute( - builder: (context) => const HomeScreen())); - } - }); - }, - ), - ), - Expanded( - child: _buildButton( - context, - 'Restore from keys', - () async { - final navigator = Navigator.of(context); - final walletState = - Provider.of(context, listen: false); - await _showKeysInputDialog(context, false, - (Exception? e) async { - if (e != null) { - throw e; - } else if (walletState.walletLoaded) { - navigator.pushReplacement(MaterialPageRoute( - builder: (context) => const HomeScreen())); - } - }); - }, - ), - ), - Expanded( - child: _buildButton( - context, - 'Watch-only', - () async { - final navigator = Navigator.of(context); - final walletState = - Provider.of(context, listen: false); - await _showKeysInputDialog(context, true, - (Exception? e) async { - if (e != null) { - throw e; - } else if (walletState.walletLoaded) { - navigator.pushReplacement(MaterialPageRoute( - builder: (context) => const HomeScreen())); - } - }); - }, - ), - ), - const Spacer(), - ], - ), - ), - ); - } - - Widget _buildButton( - BuildContext context, String text, VoidCallback onPressed) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), - child: ElevatedButton( - style: ElevatedButton.styleFrom( - textStyle: Theme.of(context).textTheme.headlineLarge, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - minimumSize: const Size(double.infinity, 60), - ), - onPressed: onPressed, - child: Text(text), - ), - ); - } -} diff --git a/lib/main.dart b/lib/main.dart index 3ef1fcf..923fcbc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,278 +1,57 @@ -import 'dart:async'; -import 'package:bitcoin_ui/bitcoin_ui.dart'; -import 'package:donationwallet/rust/api/simple.dart'; -import 'package:donationwallet/rust/frb_generated.dart'; -import 'package:donationwallet/rust/logger.dart'; +import 'package:donationwallet/src/data/providers/chain_api.dart'; +import 'package:donationwallet/src/data/providers/secure_storage.dart'; +import 'package:donationwallet/src/data/repositories/chain_repository.dart'; +import 'package:donationwallet/src/data/repositories/wallet_repository.dart'; +import 'package:donationwallet/src/domain/usecases/create_wallet_usecase.dart'; +import 'package:donationwallet/src/domain/usecases/delete_wallet_usecase.dart'; +import 'package:donationwallet/src/domain/usecases/get_chain_tip_usecase.dart'; +import 'package:donationwallet/src/domain/usecases/load_wallet_usecase.dart'; +import 'package:donationwallet/src/domain/usecases/save_wallet_usecase.dart'; +import 'package:donationwallet/src/domain/usecases/update_wallet_usecase.dart'; +import 'package:donationwallet/generated/rust/frb_generated.dart'; +import 'package:donationwallet/src/presentation/notifiers/chain_notifier.dart'; +import 'package:donationwallet/src/presentation/notifiers/wallet_notifier.dart'; +import 'package:donationwallet/src/app.dart'; -import 'package:donationwallet/global_functions.dart'; -import 'package:donationwallet/home.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:provider/provider.dart'; - -class SynchronizationService { - Timer? _timer; - final Duration _interval = const Duration(minutes: 10); - - void startSyncTimer() { - _scheduleNextTask(); - } - - void _scheduleNextTask() async { - _timer?.cancel(); - await performSynchronizationTask(); - _timer = Timer(_interval, () async { - _scheduleNextTask(); - }); - } - - Future performSynchronizationTask() async { - try { - await syncBlockchain(); - } catch (e) { - displayNotification(e.toString()); - } - } - - void stopSyncTimer() { - _timer?.cancel(); - } -} - -class WalletState extends ChangeNotifier { - final String label = "default"; - BigInt amount = BigInt.from(0); - int birthday = 0; - int lastScan = 0; - int tip = 0; - double progress = 0.0; - bool scanning = false; - String network = 'signet'; - bool walletLoaded = false; - String address = ""; - Map ownedOutputs = {}; - Map selectedOutputs = {}; - List recipients = List.empty(growable: true); - final secureStorage = const FlutterSecureStorage(); - - late StreamSubscription logStreamSubscription; - late StreamSubscription scanProgressSubscription; - late StreamSubscription amountStreamSubscription; - late StreamSubscription syncStreamSubscription; - - final _synchronizationService = SynchronizationService(); - - WalletState(); - - Future initialize() async { - try { - await _initStreams(); - _synchronizationService.startSyncTimer(); - } catch (e) { - rethrow; - } - } - - Future _initStreams() async { - logStreamSubscription = - createLogStream(level: LogLevel.debug, logDependencies: true) - .listen((event) { - print('${event.level} (${event.tag}): ${event.msg}'); - notifyListeners(); - }); - - scanProgressSubscription = createScanProgressStream().listen(((event) { - int start = event.start; - int current = event.current; - int end = event.end; - double scanned = (current - start).toDouble(); - double total = (end - start).toDouble(); - double progress = scanned / total; - if (current == end) { - progress = 0.0; - scanning = false; - } - this.progress = progress; - lastScan = current; - - notifyListeners(); - })); - - syncStreamSubscription = createSyncStream().listen((event) { - tip = event.blockheight; - - print('tip: $tip'); - - notifyListeners(); - }); - - amountStreamSubscription = createAmountStream().listen((event) { - amount = event; - notifyListeners(); - }); - } - - @override - void dispose() { - logStreamSubscription.cancel(); - scanProgressSubscription.cancel(); - amountStreamSubscription.cancel(); - syncStreamSubscription.cancel(); - _synchronizationService.stopSyncTimer(); - super.dispose(); - } - - Future reset() async { - amount = BigInt.zero; - birthday = 0; - lastScan = 0; - progress = 0.0; - scanning = false; - walletLoaded = false; - address = ""; - ownedOutputs = {}; - selectedOutputs = {}; - recipients = List.empty(growable: true); - - notifyListeners(); - } - - Future saveWalletToSecureStorage(String wallet) async { - await secureStorage.write(key: label, value: wallet); - } - - Future rmWalletFromSecureStorage() async { - await secureStorage.write(key: label, value: null); - } - - Future getWalletFromSecureStorage() async { - final wallet = await secureStorage.read(key: label); - if (wallet != null) { - return wallet; - } else { - throw Exception("No wallet in storage"); - } - } - - Future updateWalletStatus() async { - WalletStatus walletInfo; - try { - final wallet = await getWalletFromSecureStorage(); - walletInfo = getWalletInfo(encodedWallet: wallet); - } catch (e) { - rethrow; - } - address = walletInfo.address; - amount = walletInfo.balance; - birthday = walletInfo.birthday; - lastScan = walletInfo.lastScan; - ownedOutputs = walletInfo.outputs; - notifyListeners(); - } - - Map getSpendableOutputs() { - var spendable = ownedOutputs.entries.where((element) => - element.value.spendStatus == const OutputSpendStatus.unspent()); - return Map.fromEntries(spendable); - } - - void toggleOutputSelection(String outpoint, OwnedOutput output) { - if (selectedOutputs.containsKey(outpoint)) { - selectedOutputs.remove(outpoint); - } else { - selectedOutputs[outpoint] = output; - } - notifyListeners(); - } - - BigInt outputSelectionTotalAmt() { - final total = selectedOutputs.values - .fold(BigInt.zero, (sum, element) => sum + element.amount.field0); - return total; - } - - BigInt recipientTotalAmt() { - final total = recipients.fold( - BigInt.zero, (sum, element) => sum + element.amount.field0); - return total; - } - - Future addRecipients( - String address, BigInt amount, int nbOutputs) async { - final alreadyInList = recipients.where((r) => r.address == address); - if (alreadyInList.isNotEmpty) { - throw Exception("Address already in list"); - } - - if (nbOutputs < 1) { - nbOutputs = 1; - } - - if (amount <= BigInt.from(546)) { - throw Exception("Can't have amount inferior to 546 sats"); - } - recipients.add(Recipient( - address: address, - amount: Amount(field0: amount), - nbOutputs: nbOutputs)); - - notifyListeners(); - } - - Future rmRecipient(String address) async { - final alreadyInList = recipients.where((r) => r.address == address); - if (alreadyInList.isEmpty) { - throw Exception("Unknown recipient"); - } else { - recipients.removeWhere((r) => r.address == address); - } - notifyListeners(); - } - - Future scan() async { - try { - scanning = true; - await syncBlockchain(); - final wallet = await getWalletFromSecureStorage(); - final updatedWallet = await scanToTip(encodedWallet: wallet); - print(updatedWallet); - await saveWalletToSecureStorage(updatedWallet); - } catch (e) { - scanning = false; - notifyListeners(); - rethrow; - } - scanning = false; - notifyListeners(); - } -} +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await RustLib.init(); - final walletState = WalletState(); - await walletState.initialize(); - runApp( - ChangeNotifierProvider.value( - value: walletState, - child: const SilentPaymentApp(), - ), - ); + + runApp(const MyApp()); } -class SilentPaymentApp extends StatelessWidget { - const SilentPaymentApp({super.key}); +class MyApp extends StatelessWidget { + const MyApp({super.key}); @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Donation wallet', - navigatorKey: globalNavigatorKey, - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Bitcoin.green), - useMaterial3: true, - ), - home: const HomeScreen(), + AndroidOptions getAndroidOptions() => const AndroidOptions( + encryptedSharedPreferences: true, + ); + final storage = FlutterSecureStorage(aOptions: getAndroidOptions()); + return MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (_) => WalletNotifier( + SaveWalletUseCase( + WalletRepository(SecureStorageProvider(storage))), + LoadWalletUseCase( + WalletRepository(SecureStorageProvider(storage))), + DeleteWalletUseCase( + WalletRepository(SecureStorageProvider(storage))), + UpdateWalletUseCase(ChainRepository(ChainApiProvider())), + CreateWalletUseCase( + WalletRepository(SecureStorageProvider(storage))), + )), + ChangeNotifierProvider( + create: (_) => ChainNotifier( + GetChainTipUseCase(ChainRepository(ChainApiProvider())))) + ], + child: const SilentPaymentApp(), ); } } diff --git a/lib/spend.dart b/lib/spend.dart deleted file mode 100644 index 074f570..0000000 --- a/lib/spend.dart +++ /dev/null @@ -1,225 +0,0 @@ -import 'package:donationwallet/rust/api/simple.dart'; -import 'package:donationwallet/global_functions.dart'; -import 'package:donationwallet/main.dart'; -import 'package:donationwallet/outputs.dart'; -import 'package:donationwallet/destination.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class TxDestination { - final String address; - int amount; - - TxDestination({ - required this.address, - this.amount = 0, - }); -} - -class SpendingRequest { - final List inputs; - final List outputs; - final int fee; - - SpendingRequest({ - required this.inputs, - required this.outputs, - required this.fee, - }); - - Map toJson() => { - 'inputs': inputs, - 'outputs': outputs, - 'fee': fee, - }; -} - -class SummaryWidget extends StatelessWidget { - final String displayText; - final VoidCallback? onTap; - - const SummaryWidget( - {Key? key, required this.displayText, required this.onTap}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - child: Container( - width: double.infinity, - padding: const EdgeInsets.all(16.0), - margin: const EdgeInsets.symmetric(horizontal: 8.0), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8.0), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 4, - offset: const Offset(0, 3), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(displayText, style: const TextStyle(fontSize: 16)), - ], - ), - )); - } -} - -class SpendScreen extends StatelessWidget { - const SpendScreen({super.key}); - - String _newTransactionWithFees( - String wallet, - Map selectedOutputs, - List recipients, - int feeRate) { - try { - final psbt = createNewPsbt( - encodedWallet: wallet, - inputs: selectedOutputs, - recipients: recipients); - final fee = addFeeForFeeRate( - psbt: psbt, feeRate: feeRate, payer: recipients[0].address); - return fillSpOutputs(encodedWallet: wallet, psbt: fee); - } catch (e) { - rethrow; - } - } - - String _signPsbt( - String wallet, - String unsignedPsbt, - ) { - return signPsbt(encodedWallet: wallet, psbt: unsignedPsbt, finalize: true); - } - - String _broadcastSignedPsbt(String signedPsbt) { - try { - final tx = extractTxFromPsbt(psbt: signedPsbt); - print(tx); - final txid = broadcastTx(tx: tx); - return txid; - } catch (e) { - rethrow; - } - } - - String _markAsSpent( - String wallet, String txid, Map selectedOutputs) { - try { - final updatedWallet = markOutpointsSpent( - encodedWallet: wallet, - spentBy: txid, - spent: selectedOutputs.keys.toList()); - return updatedWallet; - } catch (e) { - rethrow; - } - } - - @override - Widget build(BuildContext context) { - final TextEditingController feeRateController = TextEditingController(); - - final walletState = Provider.of(context); - final selectedOutputs = walletState.selectedOutputs; - final recipients = walletState.recipients; - - return Scaffold( - appBar: AppBar( - title: const Text('New Transaction'), - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Spacer(), - SummaryWidget( - displayText: selectedOutputs.isEmpty - ? "Tap here to choose which coin to spend" - : "Spending ${selectedOutputs.length} output(s) for a total of ${walletState.outputSelectionTotalAmt()} sats available", - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const OutputsScreen()), - ); - }), - const Spacer(), - SummaryWidget( - displayText: recipients.isEmpty - ? "Tap here to add destinations" - : "Sending to ${recipients.length} output(s) for a total of ${walletState.recipientTotalAmt()} sats", - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const DestinationScreen()), - ); - }), - const Spacer(), - const Text('Fee Rate (satoshis/vB)'), - TextField( - controller: feeRateController, - keyboardType: - const TextInputType.numberWithOptions(decimal: false), - decoration: const InputDecoration( - hintText: 'Enter fee rate', - ), - ), - const Spacer(), - ElevatedButton( - onPressed: () async { - final int? fees = int.tryParse(feeRateController.text); - if (fees == null) { - throw Exception("No fees input"); - } - final walletState = - Provider.of(context, listen: false); - final wallet = await walletState.getWalletFromSecureStorage(); - try { - final unsignedPsbt = _newTransactionWithFees( - wallet, - walletState.selectedOutputs, - walletState.recipients, - fees); - final signedPsbt = _signPsbt(wallet, unsignedPsbt); - final sentTxId = _broadcastSignedPsbt(signedPsbt); - final updatedWallet = _markAsSpent( - wallet, sentTxId, walletState.selectedOutputs); - - // Clear selections - walletState.selectedOutputs.clear(); - walletState.recipients.clear(); - - // save the updated wallet - walletState.saveWalletToSecureStorage(updatedWallet); - - if (!context.mounted) return; - - // navigate to main screen - Navigator.popUntil(context, (route) => route.isFirst); - - showAlertDialog('Transaction successfully sent', sentTxId); - } catch (e) { - rethrow; - } - }, - style: ElevatedButton.styleFrom( - minimumSize: const Size(double.infinity, 50), - ), - child: const Text('Spend'), - ), - const Spacer() - ], - ), - ), - ); - } -} diff --git a/lib/src/app.dart b/lib/src/app.dart new file mode 100644 index 0000000..16efd87 --- /dev/null +++ b/lib/src/app.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:donationwallet/src/utils/global_functions.dart'; +import 'package:donationwallet/src/presentation/screens/home_screen.dart'; +import 'package:donationwallet/src/presentation/theme/app_theme.dart'; + +class SilentPaymentApp extends StatelessWidget { + const SilentPaymentApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Donation Wallet', + navigatorKey: globalNavigatorKey, + theme: appTheme, + home: const HomeScreen(), + ); + } +} diff --git a/lib/src/data/models/label_model.dart b/lib/src/data/models/label_model.dart new file mode 100644 index 0000000..a4c8de2 --- /dev/null +++ b/lib/src/data/models/label_model.dart @@ -0,0 +1,20 @@ +class Label { + final String label; + final List pubkey; + + Label({required this.label, required this.pubkey}); + + factory Label.fromJson(Map json) { + return Label( + label: json['0'] as String, + pubkey: List.from(json['1'] as List), + ); + } + + Map toJson() { + return { + '0': label, + '1': pubkey, + }; + } +} diff --git a/lib/src/data/models/outputs_model.dart b/lib/src/data/models/outputs_model.dart new file mode 100644 index 0000000..19dea20 --- /dev/null +++ b/lib/src/data/models/outputs_model.dart @@ -0,0 +1,38 @@ +import 'package:donationwallet/generated/rust/api/simple.dart'; + +class Outputs { + final List walletFingerprint; + final int birthday; + final int lastScan; + final Map outputs; + + Outputs({ + required this.walletFingerprint, + required this.birthday, + required this.lastScan, + required this.outputs, + }); + + factory Outputs.fromJson(Map json) { + var outputsMap = json['outputs'] as Map; + Map outputs = outputsMap.map( + (key, value) => MapEntry(key, value), + ); + + return Outputs( + walletFingerprint: List.from(json['wallet_fingerprint']), + birthday: json['birthday'], + lastScan: json['last_scan'], + outputs: outputs, + ); + } + + Map toJson() { + return { + 'wallet_fingerprint': walletFingerprint, + 'birthday': birthday, + 'last_scan': lastScan, + 'outputs': outputs, + }; + } +} diff --git a/lib/src/data/models/ownedoutput_model.dart b/lib/src/data/models/ownedoutput_model.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/src/data/models/recipient_model.dart b/lib/src/data/models/recipient_model.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/src/data/models/sp_client_model.dart b/lib/src/data/models/sp_client_model.dart new file mode 100644 index 0000000..dc63699 --- /dev/null +++ b/lib/src/data/models/sp_client_model.dart @@ -0,0 +1,38 @@ +import 'package:donationwallet/src/data/models/spend_key_model.dart'; +import 'package:donationwallet/src/data/models/sp_receiver_model.dart'; + +class SpClient { + final String label; + final String scanSk; + final SpendKey spendKey; + final String mnemonic; + final SPReceiver spReceiver; + + SpClient({ + required this.label, + required this.scanSk, + required this.spendKey, + required this.mnemonic, + required this.spReceiver, + }); + + factory SpClient.fromJson(Map json) { + return SpClient( + label: json['label'], + scanSk: json['scan_sk'], + spendKey: SpendKey.fromJson(json['spend_key']), + mnemonic: json['mnemonic'], + spReceiver: SPReceiver.fromJson(json['sp_receiver']), + ); + } + + Map toJson() { + return { + 'label': label, + 'scan_sk': scanSk, + 'spend_key': spendKey.toJson(), + 'mnemonic': mnemonic, + 'sp_receiver': spReceiver.toJson(), + }; + } +} diff --git a/lib/src/data/models/sp_receiver_model.dart b/lib/src/data/models/sp_receiver_model.dart new file mode 100644 index 0000000..ea03bed --- /dev/null +++ b/lib/src/data/models/sp_receiver_model.dart @@ -0,0 +1,48 @@ +import 'package:donationwallet/src/data/models/label_model.dart'; + +class SPReceiver { + final int version; + final String network; + final List scanPubkey; + final List spendPubkey; + final String changeLabel; + final List