diff --git a/lib/providers/wallet_provider.dart b/lib/providers/wallet_provider.dart index 7288d38..3308a60 100644 --- a/lib/providers/wallet_provider.dart +++ b/lib/providers/wallet_provider.dart @@ -87,8 +87,8 @@ class WalletProvider extends ChangeNotifier { static const _mintsKey = 'wallet_mints'; static const _activeMintKey = 'wallet_active_mint'; static const _activeUnitKey = 'wallet_active_unit'; - static const _pendingMintInvoicesKey = 'pending_mint_invoices'; static const _pendingNostrRequestKey = 'pending_nostr_request'; + static const _legacyPendingMintInvoicesKey = 'pending_mint_invoices'; /// Mint de Cuba Bitcoin - siempre aparece primero en la lista static const cubaBitcoinMint = 'https://mint.cubabitcoin.org'; @@ -1330,8 +1330,6 @@ class WalletProvider extends ChangeNotifier { throw Exception('No hay wallet activo'); } - final mintUrl = _activeMintUrl!; - final unit = _activeUnit; String? invoiceBolt11; // Cerrar controller anterior si existe (evitar leak) @@ -1355,10 +1353,8 @@ class WalletProvider extends ChangeNotifier { description: description, ).listen( (quote) { - // Guardar invoice temprano en SharedPreferences if (quote.state == MintQuoteState.unpaid) { invoiceBolt11 = quote.request; - _savePendingMintInvoice(quote.id, quote.request, mintUrl, unit, amount); } // Reenviar a la UI ANTES de cualquier await: Rust cierra el stream @@ -1372,13 +1368,7 @@ class WalletProvider extends ChangeNotifier { // Metadata async fire-and-forget (no bloquea el forward) if (quote.state == MintQuoteState.issued && invoiceBolt11 != null) { final invoice = invoiceBolt11!; - unawaited( - _saveMintMetadata(wallet, invoice, quote.transactionId).then((saved) { - // Only remove pending invoice if metadata was saved; - // otherwise _matchPendingMintInvoices can recover it on next startup - if (saved) _removePendingMintInvoice(quote.id); - }), - ); + unawaited(_saveMintMetadata(wallet, invoice, quote.transactionId)); } }, onError: (error) { @@ -1397,91 +1387,63 @@ class WalletProvider extends ChangeNotifier { return controller.stream; } - /// Guarda un invoice de mint pendiente para recuperarlo después. - Future _savePendingMintInvoice( - String quoteId, String invoice, String mintUrl, String unit, BigInt amount, - ) async { + /// Exact recovery of paid-but-unissued mint quotes using `quote_id`. + /// + /// Replaces the old heuristic that matched by (mintUrl, unit, amount) and + /// could pair a freshly created invoice to an unrelated orphan transaction + /// of the same amount. For every unissued quote the CDK knows about, we + /// ask the mint for the current state and — if Paid — issue the proofs, + /// linking the resulting tx_id to its bolt11 in TransactionMeta. + Future _recoverPendingMintQuotes() async { + // One-time cleanup of the legacy SharedPreferences state (no-op after + // the first run). Only writes when the key is actually present. try { final prefs = await SharedPreferences.getInstance(); - final jsonStr = prefs.getString(_pendingMintInvoicesKey) ?? '{}'; - final map = Map.from(jsonDecode(jsonStr)); - map[quoteId] = { - 'invoice': invoice, - 'mintUrl': mintUrl, - 'unit': unit, - 'amount': amount.toString(), - 'createdAt': DateTime.now().toIso8601String(), - }; - await prefs.setString(_pendingMintInvoicesKey, jsonEncode(map)); - debugPrint('Pending mint invoice guardado: $quoteId'); + if (prefs.getString(_legacyPendingMintInvoicesKey) != null) { + await prefs.remove(_legacyPendingMintInvoicesKey); + debugPrint('Removed legacy $_legacyPendingMintInvoicesKey from prefs'); + } } catch (e) { - debugPrint('Error guardando pending mint invoice: $e'); + debugPrint('Legacy pending-invoices cleanup failed: $e'); } - } - /// Elimina un invoice de mint pendiente (ya fue procesado). - Future _removePendingMintInvoice(String quoteId) async { - try { - final prefs = await SharedPreferences.getInstance(); - final jsonStr = prefs.getString(_pendingMintInvoicesKey) ?? '{}'; - final map = Map.from(jsonDecode(jsonStr)); - map.remove(quoteId); - await prefs.setString(_pendingMintInvoicesKey, jsonEncode(map)); - debugPrint('Pending mint invoice eliminado: $quoteId'); - } catch (e) { - debugPrint('Error eliminando pending mint invoice: $e'); - } - } + for (final entry in _mintUnits.entries) { + for (final unit in entry.value) { + try { + final wallet = await getWallet(entry.key, unit); + final pending = await wallet.getUnissuedMintQuotes(); + for (final quote in pending) { + try { + final updated = await wallet.checkMintQuoteStatus(quoteId: quote.id); + if (updated.state != MintQuoteState.paid) continue; - /// TTL para invoices de mint pendientes (24 horas). - static const _pendingMintInvoiceTtl = Duration(hours: 24); - - /// Obtiene todos los invoices de mint pendientes, limpiando expirados. - Future> _getPendingMintInvoices() async { - try { - final prefs = await SharedPreferences.getInstance(); - final jsonStr = prefs.getString(_pendingMintInvoicesKey) ?? '{}'; - final map = Map.from(jsonDecode(jsonStr)); - - // Filtrar expirados - final now = DateTime.now(); - final expired = []; - for (final entry in map.entries) { - final data = Map.from(entry.value); - final createdAt = DateTime.tryParse(data['createdAt'] ?? ''); - if (createdAt == null || now.difference(createdAt) > _pendingMintInvoiceTtl) { - expired.add(entry.key); + final minted = await wallet.mintByQuoteId(quoteId: quote.id); + final txId = minted.transactionId; + if (txId != null && txId.isNotEmpty) { + await _txMetaStorage.save( + txId, + TransactionMeta( + type: TransactionType.lightning, + invoice: updated.request, + ), + ); + debugPrint('Recovered mint quote ${quote.id} → tx $txId'); + } else { + debugPrint('Recovered mint quote ${quote.id} but no tx_id'); + } + confettiController.fire(); + notifyListeners(); + } catch (e) { + debugPrint('Recover quote ${quote.id} failed: $e'); + } + } + } catch (e) { + debugPrint('Recover quotes for ${entry.key}/$unit failed: $e'); } } - - // Limpiar expirados si hay - if (expired.isNotEmpty) { - for (final key in expired) { - map.remove(key); - } - await prefs.setString(_pendingMintInvoicesKey, jsonEncode(map)); - debugPrint('Pending mint invoices expirados eliminados: ${expired.length}'); - } - - return map; - } catch (e) { - return {}; } } - /// Busca un invoice pendiente que coincida con un mintUrl y unit. - /// Retorna el invoice string o null. - Future findPendingMintInvoice(String mintUrl, String unit) async { - final pending = await _getPendingMintInvoices(); - for (final entry in pending.values) { - final data = Map.from(entry); - if (data['mintUrl'] == mintUrl && data['unit'] == unit) { - return data['invoice'] as String?; - } - } - return null; - } - // ============================================================ // PENDING NOSTR PAYMENT REQUESTS // ============================================================ @@ -1916,63 +1878,13 @@ class WalletProvider extends ChangeNotifier { } } - // Vincular transacciones incoming sin metadata con pending invoices - await _matchPendingMintInvoices(); + // Recover paid-but-unissued quotes by quote_id (exact, no heuristics) + await _recoverPendingMintQuotes(); // Resume pending Nostr payment request if app was killed mid-wait await resumePendingNostrRequest(); } - /// Busca transacciones incoming sin metadata y las vincula con - /// invoices de mint pendientes guardados en SharedPreferences. - Future _matchPendingMintInvoices() async { - try { - final pending = await _getPendingMintInvoices(); - if (pending.isEmpty) return; - - // Obtener todas las transacciones incoming - final allTxs = await getAllTransactions( - direction: TransactionDirection.incoming, - ); - - for (final tx in allTxs) { - // Solo procesar transacciones sin metadata - if (_txMetaStorage.has(tx.id)) continue; - - // Buscar un pending invoice que coincida con mintUrl, unit y amount - String? matchedQuoteId; - String? matchedInvoice; - - for (final entry in pending.entries) { - final data = Map.from(entry.value); - if (data['mintUrl'] == tx.mintUrl && data['unit'] == tx.unit && data['amount'] == tx.amount.toString()) { - matchedQuoteId = entry.key; - matchedInvoice = data['invoice'] as String?; - break; - } - } - - if (matchedQuoteId != null && matchedInvoice != null) { - await _txMetaStorage.save( - tx.id, - TransactionMeta( - type: TransactionType.lightning, - invoice: matchedInvoice, - ), - ); - // Limpiar el pending invoice ya vinculado - await _removePendingMintInvoice(matchedQuoteId); - pending.remove(matchedQuoteId); - debugPrint('Matched pending mint invoice → tx ${tx.id}'); - confettiController.fire(); - notifyListeners(); - } - } - } catch (e) { - debugPrint('Error matching pending mint invoices: $e'); - } - } - /// Verifica si un token específico fue gastado. Future isTokenSpent(String encodedToken) async { final wallet = activeWallet; diff --git a/lib/screens/9_history/history_screen.dart b/lib/screens/9_history/history_screen.dart index a785d48..9034dbb 100644 --- a/lib/screens/9_history/history_screen.dart +++ b/lib/screens/9_history/history_screen.dart @@ -782,7 +782,6 @@ class _TransactionDetailScreenState extends State<_TransactionDetailScreen> { late final bool _isLightning; String? _tokenOrInvoice; late final bool _shouldShowQR; - bool _isLoadingPendingInvoice = false; bool _isReclaiming = false; @override @@ -795,11 +794,6 @@ class _TransactionDetailScreenState extends State<_TransactionDetailScreen> { final meta = widget.walletProvider.getTransactionMeta(widget.transaction.id); _tokenOrInvoice = meta?.token ?? meta?.invoice; - // Si es Lightning incoming sin invoice, buscar en pending mint invoices - if (_tokenOrInvoice == null && _isLightning && _isIncoming) { - _loadPendingMintInvoice(); - } - // Mostrar QR para todo EXCEPTO Lightning saliente _shouldShowQR = !_isLightning || _isIncoming; @@ -809,26 +803,6 @@ class _TransactionDetailScreenState extends State<_TransactionDetailScreen> { } } - /// Busca el invoice en pending mint invoices si no hay metadata. - Future _loadPendingMintInvoice() async { - setState(() => _isLoadingPendingInvoice = true); - String? invoice; - try { - invoice = await widget.walletProvider.findPendingMintInvoice( - widget.transaction.mintUrl, - widget.transaction.unit, - ); - } catch (e, st) { - debugPrint('findPendingMintInvoice failed: $e\n$st'); - } - if (mounted) { - setState(() { - _isLoadingPendingInvoice = false; - if (invoice != null) _tokenOrInvoice = invoice; - }); - } - } - @override void dispose() { _animationTimer?.cancel(); @@ -968,7 +942,7 @@ class _TransactionDetailScreenState extends State<_TransactionDetailScreen> { ], // Mensaje si no hay token/invoice (ocultar mientras carga) - if (_tokenOrInvoice == null && !_isLoadingPendingInvoice) ...[ + if (_tokenOrInvoice == null) ...[ Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( diff --git a/lib/src/rust/api/wallet.dart b/lib/src/rust/api/wallet.dart index 9e16c8c..2a56a6f 100644 --- a/lib/src/rust/api/wallet.dart +++ b/lib/src/rust/api/wallet.dart @@ -70,6 +70,11 @@ abstract class Wallet implements RustOpaqueInterface { Future checkAllMintQuotes(); + /// Ask the mint for the current state of a single quote by id and + /// return the refreshed MintQuote. Local store is updated as a side + /// effect inside CDK. + Future checkMintQuoteStatus({required String quoteId}); + Future checkPendingTransactions(); /// Observe-only counterpart of `reclaim_proofs_by_ys`. Queries the mint @@ -105,6 +110,10 @@ abstract class Wallet implements RustOpaqueInterface { Future getMint(); + /// Return pending (unissued) mint quotes known to the local store, + /// filtered to this wallet's mint/unit. + Future> getUnissuedMintQuotes(); + Future isTokenSpent({required Token token}); Future> listTransactions({TransactionDirection? direction}); @@ -113,6 +122,12 @@ abstract class Wallet implements RustOpaqueInterface { Stream mint({required BigInt amount, String? description}); + /// Mint proofs for a paid quote and return an Issued MintQuote with + /// `transaction_id` and `token` populated — same shape the `mint()` + /// stream emits on Issued. Use this to recover metadata for quotes + /// that were paid while the app was killed. + Future mintByQuoteId({required String quoteId}); + factory Wallet({ required String mintUrl, required String unit, diff --git a/lib/src/rust/frb_generated.dart b/lib/src/rust/frb_generated.dart index cc43a9e..88e5d07 100644 --- a/lib/src/rust/frb_generated.dart +++ b/lib/src/rust/frb_generated.dart @@ -69,7 +69,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.11.1'; @override - int get rustContentHash => 1593022460; + int get rustContentHash => 884570592; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -246,6 +246,11 @@ abstract class RustLibApi extends BaseApi { Future crateApiWalletWalletCheckAllMintQuotes({required Wallet that}); + Future crateApiWalletWalletCheckMintQuoteStatus({ + required Wallet that, + required String quoteId, + }); + Future crateApiWalletWalletCheckPendingTransactions({ required Wallet that, }); @@ -269,6 +274,10 @@ abstract class RustLibApi extends BaseApi { Future crateApiWalletWalletGetMint({required Wallet that}); + Future> crateApiWalletWalletGetUnissuedMintQuotes({ + required Wallet that, + }); + Future crateApiWalletWalletIsTokenSpent({ required Wallet that, required Token token, @@ -290,6 +299,11 @@ abstract class RustLibApi extends BaseApi { String? description, }); + Future crateApiWalletWalletMintByQuoteId({ + required Wallet that, + required String quoteId, + }); + Wallet crateApiWalletWalletNew({ required String mintUrl, required String unit, @@ -1772,6 +1786,44 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["that"], ); + @override + Future crateApiWalletWalletCheckMintQuoteStatus({ + required Wallet that, + required String quoteId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerWallet( + that, + serializer, + ); + sse_encode_String(quoteId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 41, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_mint_quote, + decodeErrorData: sse_decode_error, + ), + constMeta: kCrateApiWalletWalletCheckMintQuoteStatusConstMeta, + argValues: [that, quoteId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWalletWalletCheckMintQuoteStatusConstMeta => + const TaskConstMeta( + debugName: "Wallet_check_mint_quote_status", + argNames: ["that", "quoteId"], + ); + @override Future crateApiWalletWalletCheckPendingTransactions({ required Wallet that, @@ -1787,7 +1839,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 41, + funcId: 42, port: port_, ); }, @@ -1825,7 +1877,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 42, + funcId: 43, port: port_, ); }, @@ -1866,7 +1918,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 43, + funcId: 44, port: port_, ); }, @@ -1904,7 +1956,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 44, + funcId: 45, port: port_, ); }, @@ -1941,7 +1993,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 45, + funcId: 46, port: port_, ); }, @@ -1975,7 +2027,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 46, + funcId: 47, port: port_, ); }, @@ -1993,6 +2045,42 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { TaskConstMeta get kCrateApiWalletWalletGetMintConstMeta => const TaskConstMeta(debugName: "Wallet_get_mint", argNames: ["that"]); + @override + Future> crateApiWalletWalletGetUnissuedMintQuotes({ + required Wallet that, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerWallet( + that, + serializer, + ); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 48, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_list_mint_quote, + decodeErrorData: sse_decode_error, + ), + constMeta: kCrateApiWalletWalletGetUnissuedMintQuotesConstMeta, + argValues: [that], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWalletWalletGetUnissuedMintQuotesConstMeta => + const TaskConstMeta( + debugName: "Wallet_get_unissued_mint_quotes", + argNames: ["that"], + ); + @override Future crateApiWalletWalletIsTokenSpent({ required Wallet that, @@ -2010,7 +2098,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 47, + funcId: 49, port: port_, ); }, @@ -2051,7 +2139,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 48, + funcId: 50, port: port_, ); }, @@ -2089,7 +2177,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 49, + funcId: 51, port: port_, ); }, @@ -2132,7 +2220,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 50, + funcId: 52, port: port_, ); }, @@ -2154,6 +2242,44 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { argNames: ["that", "amount", "description", "sink"], ); + @override + Future crateApiWalletWalletMintByQuoteId({ + required Wallet that, + required String quoteId, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerWallet( + that, + serializer, + ); + sse_encode_String(quoteId, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 53, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_mint_quote, + decodeErrorData: sse_decode_error, + ), + constMeta: kCrateApiWalletWalletMintByQuoteIdConstMeta, + argValues: [that, quoteId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWalletWalletMintByQuoteIdConstMeta => + const TaskConstMeta( + debugName: "Wallet_mint_by_quote_id", + argNames: ["that", "quoteId"], + ); + @override Wallet crateApiWalletWalletNew({ required String mintUrl, @@ -2174,7 +2300,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { db, serializer, ); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 51)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 54)!; }, codec: SseCodec( decodeSuccessData: @@ -2212,7 +2338,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 52, + funcId: 55, port: port_, ); }, @@ -2250,7 +2376,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 53, + funcId: 56, port: port_, ); }, @@ -2291,7 +2417,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 54, + funcId: 57, port: port_, ); }, @@ -2332,7 +2458,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 55, + funcId: 58, port: port_, ); }, @@ -2368,7 +2494,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 56, + funcId: 59, port: port_, ); }, @@ -2406,7 +2532,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 57, + funcId: 60, port: port_, ); }, @@ -2442,7 +2568,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 58, + funcId: 61, port: port_, ); }, @@ -2476,7 +2602,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 59, + funcId: 62, port: port_, ); }, @@ -2510,7 +2636,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 60, + funcId: 63, port: port_, ); }, @@ -2552,7 +2678,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 61, + funcId: 64, port: port_, ); }, @@ -2588,7 +2714,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 62, + funcId: 65, port: port_, ); }, @@ -2634,7 +2760,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 63, + funcId: 66, port: port_, ); }, @@ -2672,7 +2798,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { sse_encode_String(proofsJson, serializer); sse_encode_opt_String(memo, serializer); sse_encode_opt_String(unit, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 64)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 67)!; }, codec: SseCodec( decodeSuccessData: sse_decode_token, @@ -2702,7 +2828,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_box_autoadd_token(token, serializer); sse_encode_opt_box_autoadd_usize(maxFragmentLength, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 65)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 68)!; }, codec: SseCodec( decodeSuccessData: sse_decode_list_String, @@ -2732,7 +2858,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 66, + funcId: 69, port: port_, ); }, @@ -2756,7 +2882,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { SyncTask( callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 67)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 70)!; }, codec: SseCodec( decodeSuccessData: sse_decode_String, @@ -2782,7 +2908,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 68, + funcId: 71, port: port_, ); }, @@ -2807,7 +2933,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(secret, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 69)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 72)!; }, codec: SseCodec( decodeSuccessData: sse_decode_String, @@ -2830,7 +2956,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(mnemonic, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 70)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 73)!; }, codec: SseCodec( decodeSuccessData: sse_decode_list_prim_u_8_strict, @@ -2857,7 +2983,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(encoded, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 71)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 74)!; }, codec: SseCodec( decodeSuccessData: sse_decode_payment_request_info, @@ -2886,7 +3012,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 72, + funcId: 75, port: port_, ); }, @@ -2913,7 +3039,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 73, + funcId: 76, port: port_, ); }, @@ -2940,7 +3066,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 74, + funcId: 77, port: port_, ); }, @@ -2965,7 +3091,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_list_prim_u_8_loose(raw, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 75)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 78)!; }, codec: SseCodec( decodeSuccessData: sse_decode_token, @@ -2988,7 +3114,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { callFfi: () { final serializer = SseSerializer(generalizedFrbRustBinding); sse_encode_String(encoded, serializer); - return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 76)!; + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 79)!; }, codec: SseCodec( decodeSuccessData: sse_decode_token, @@ -3513,6 +3639,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return (raw as List).map(dco_decode_mint_method_settings).toList(); } + @protected + List dco_decode_list_mint_quote(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_mint_quote).toList(); + } + @protected List dco_decode_list_prim_u_8_loose(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -4553,6 +4685,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return ans_; } + @protected + List sse_decode_list_mint_quote(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_mint_quote(deserializer)); + } + return ans_; + } + @protected List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer) { // Codec=Sse (Serialization based), see doc to use other codecs @@ -5778,6 +5922,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_list_mint_quote( + List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_mint_quote(item, serializer); + } + } + @protected void sse_encode_list_prim_u_8_loose( List self, @@ -6592,6 +6748,14 @@ class WalletImpl extends RustOpaque implements Wallet { Future checkAllMintQuotes() => RustLib.instance.api.crateApiWalletWalletCheckAllMintQuotes(that: this); + /// Ask the mint for the current state of a single quote by id and + /// return the refreshed MintQuote. Local store is updated as a side + /// effect inside CDK. + Future checkMintQuoteStatus({required String quoteId}) => RustLib + .instance + .api + .crateApiWalletWalletCheckMintQuoteStatus(that: this, quoteId: quoteId); + Future checkPendingTransactions() => RustLib.instance.api .crateApiWalletWalletCheckPendingTransactions(that: this); @@ -6639,6 +6803,11 @@ class WalletImpl extends RustOpaque implements Wallet { Future getMint() => RustLib.instance.api.crateApiWalletWalletGetMint(that: this); + /// Return pending (unissued) mint quotes known to the local store, + /// filtered to this wallet's mint/unit. + Future> getUnissuedMintQuotes() => RustLib.instance.api + .crateApiWalletWalletGetUnissuedMintQuotes(that: this); + Future isTokenSpent({required Token token}) => RustLib.instance.api .crateApiWalletWalletIsTokenSpent(that: this, token: token); @@ -6659,6 +6828,15 @@ class WalletImpl extends RustOpaque implements Wallet { description: description, ); + /// Mint proofs for a paid quote and return an Issued MintQuote with + /// `transaction_id` and `token` populated — same shape the `mint()` + /// stream emits on Issued. Use this to recover metadata for quotes + /// that were paid while the app was killed. + Future mintByQuoteId({required String quoteId}) => RustLib + .instance + .api + .crateApiWalletWalletMintByQuoteId(that: this, quoteId: quoteId); + /// Pay a NUT-18 payment request. /// Uses CDK's pay_request() which handles: /// - NUT-10 spending conditions diff --git a/lib/src/rust/frb_generated.io.dart b/lib/src/rust/frb_generated.io.dart index 4af1c1b..a28c25f 100644 --- a/lib/src/rust/frb_generated.io.dart +++ b/lib/src/rust/frb_generated.io.dart @@ -299,6 +299,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List dco_decode_list_mint_method_settings(dynamic raw); + @protected + List dco_decode_list_mint_quote(dynamic raw); + @protected List dco_decode_list_prim_u_8_loose(dynamic raw); @@ -706,6 +709,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + List sse_decode_list_mint_quote(SseDeserializer deserializer); + @protected List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer); @@ -1200,6 +1206,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_list_mint_quote( + List self, + SseSerializer serializer, + ); + @protected void sse_encode_list_prim_u_8_loose(List self, SseSerializer serializer); diff --git a/lib/src/rust/frb_generated.web.dart b/lib/src/rust/frb_generated.web.dart index e92bc7f..6e930b6 100644 --- a/lib/src/rust/frb_generated.web.dart +++ b/lib/src/rust/frb_generated.web.dart @@ -301,6 +301,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected List dco_decode_list_mint_method_settings(dynamic raw); + @protected + List dco_decode_list_mint_quote(dynamic raw); + @protected List dco_decode_list_prim_u_8_loose(dynamic raw); @@ -708,6 +711,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseDeserializer deserializer, ); + @protected + List sse_decode_list_mint_quote(SseDeserializer deserializer); + @protected List sse_decode_list_prim_u_8_loose(SseDeserializer deserializer); @@ -1202,6 +1208,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_list_mint_quote( + List self, + SseSerializer serializer, + ); + @protected void sse_encode_list_prim_u_8_loose(List self, SseSerializer serializer); diff --git a/rust/src/api/wallet.rs b/rust/src/api/wallet.rs index 722b785..fdca9ad 100644 --- a/rust/src/api/wallet.rs +++ b/rust/src/api/wallet.rs @@ -411,6 +411,62 @@ impl Wallet { Ok(()) } + /// Return pending (unissued) mint quotes known to the local store, + /// filtered to this wallet's mint/unit. + pub async fn get_unissued_mint_quotes(&self) -> Result, Error> { + let quotes = self.inner.get_unissued_mint_quotes().await?; + Ok(quotes.into_iter().map(MintQuote::from).collect()) + } + + /// Ask the mint for the current state of a single quote by id and + /// return the refreshed MintQuote. Local store is updated as a side + /// effect inside CDK. + pub async fn check_mint_quote_status(&self, quote_id: String) -> Result { + let quote = self.inner.check_mint_quote_status("e_id).await?; + Ok(MintQuote::from(quote)) + } + + /// Mint proofs for a paid quote and return an Issued MintQuote with + /// `transaction_id` and `token` populated — same shape the `mint()` + /// stream emits on Issued. Use this to recover metadata for quotes + /// that were paid while the app was killed. + pub async fn mint_by_quote_id(&self, quote_id: String) -> Result { + let mint_url = self.mint_url()?; + let unit = self.unit(); + let cdk_quote = self.inner.check_mint_quote_status("e_id).await?; + let expiry = cdk_quote.expiry; + let request = cdk_quote.request.clone(); + + let mint_proofs = self + .inner + .mint("e_id, SplitTarget::None, None) + .await?; + + let tx_id = match TransactionId::try_from(mint_proofs.clone()) { + Ok(id) => Some(id.to_string()), + Err(e) => { + info!("Failed to compute mint tx ID: {e}"); + None + } + }; + + let mint_amount = mint_proofs.total_amount().unwrap_or_default(); + let token = Token::try_from(CdkToken::new(mint_url, mint_proofs, None, unit)).ok(); + + self.update_balance_streams().await; + + Ok(MintQuote { + id: quote_id, + request, + amount: Some(mint_amount.into()), + expiry: Some(expiry), + state: CdkMintQuoteState::Issued.into(), + token, + error: None, + transaction_id: tx_id, + }) + } + // === Lightning Withdrawal (NUT-05) === pub async fn melt_quote(&self, request: String) -> Result { diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index 24bf71e..7aff410 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -40,7 +40,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueMoi, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1593022460; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 884570592; // Section: executor @@ -1985,6 +1985,66 @@ fn wire__crate__api__wallet__Wallet_check_all_mint_quotes_impl( }, ) } +fn wire__crate__api__wallet__Wallet_check_mint_quote_status_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "Wallet_check_mint_quote_status", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_quote_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::api::error::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::wallet::Wallet::check_mint_quote_status( + &*api_that_guard, + api_quote_id, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__wallet__Wallet_check_pending_transactions_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -2335,6 +2395,63 @@ fn wire__crate__api__wallet__Wallet_get_mint_impl( }, ) } +fn wire__crate__api__wallet__Wallet_get_unissued_mint_quotes_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "Wallet_get_unissued_mint_quotes", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::api::error::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = + crate::api::wallet::Wallet::get_unissued_mint_quotes(&*api_that_guard) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__wallet__Wallet_is_token_spent_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -2579,6 +2696,66 @@ fn wire__crate__api__wallet__Wallet_mint_impl( }, ) } +fn wire__crate__api__wallet__Wallet_mint_by_quote_id_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "Wallet_mint_by_quote_id", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_quote_id = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::api::error::Error>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::api::wallet::Wallet::mint_by_quote_id( + &*api_that_guard, + api_quote_id, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__wallet__Wallet_new_impl( ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, rust_vec_len_: i32, @@ -4158,6 +4335,18 @@ impl SseDecode for Vec { } } +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -4833,94 +5022,112 @@ fn pde_ffi_dispatcher_primary_impl( rust_vec_len, data_len, ), - 41 => wire__crate__api__wallet__Wallet_check_pending_transactions_impl( + 41 => wire__crate__api__wallet__Wallet_check_mint_quote_status_impl( port, ptr, rust_vec_len, data_len, ), - 42 => wire__crate__api__wallet__Wallet_check_proofs_by_ys_impl( + 42 => wire__crate__api__wallet__Wallet_check_pending_transactions_impl( port, ptr, rust_vec_len, data_len, ), - 43 => wire__crate__api__wallet__Wallet_confirm_melt_impl(port, ptr, rust_vec_len, data_len), - 44 => wire__crate__api__wallet__Wallet_create_payment_request_impl( + 43 => wire__crate__api__wallet__Wallet_check_proofs_by_ys_impl( port, ptr, rust_vec_len, data_len, ), - 45 => wire__crate__api__wallet__Wallet_finalize_pending_melts_impl( + 44 => wire__crate__api__wallet__Wallet_confirm_melt_impl(port, ptr, rust_vec_len, data_len), + 45 => wire__crate__api__wallet__Wallet_create_payment_request_impl( port, ptr, rust_vec_len, data_len, ), - 46 => wire__crate__api__wallet__Wallet_get_mint_impl(port, ptr, rust_vec_len, data_len), - 47 => { + 46 => wire__crate__api__wallet__Wallet_finalize_pending_melts_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 47 => wire__crate__api__wallet__Wallet_get_mint_impl(port, ptr, rust_vec_len, data_len), + 48 => wire__crate__api__wallet__Wallet_get_unissued_mint_quotes_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 49 => { wire__crate__api__wallet__Wallet_is_token_spent_impl(port, ptr, rust_vec_len, data_len) } - 48 => wire__crate__api__wallet__Wallet_list_transactions_impl( + 50 => wire__crate__api__wallet__Wallet_list_transactions_impl( port, ptr, rust_vec_len, data_len, ), - 49 => wire__crate__api__wallet__Wallet_melt_quote_impl(port, ptr, rust_vec_len, data_len), - 50 => wire__crate__api__wallet__Wallet_mint_impl(port, ptr, rust_vec_len, data_len), - 52 => wire__crate__api__wallet__Wallet_pay_payment_request_impl( + 51 => wire__crate__api__wallet__Wallet_melt_quote_impl(port, ptr, rust_vec_len, data_len), + 52 => wire__crate__api__wallet__Wallet_mint_impl(port, ptr, rust_vec_len, data_len), + 53 => wire__crate__api__wallet__Wallet_mint_by_quote_id_impl( port, ptr, rust_vec_len, data_len, ), - 53 => wire__crate__api__wallet__Wallet_prepare_melt_impl(port, ptr, rust_vec_len, data_len), - 54 => wire__crate__api__wallet__Wallet_prepare_send_impl(port, ptr, rust_vec_len, data_len), - 55 => wire__crate__api__wallet__Wallet_receive_impl(port, ptr, rust_vec_len, data_len), - 56 => wire__crate__api__wallet__Wallet_reclaim_pending_proofs_impl( + 55 => wire__crate__api__wallet__Wallet_pay_payment_request_impl( port, ptr, rust_vec_len, data_len, ), - 57 => wire__crate__api__wallet__Wallet_reclaim_proofs_by_ys_impl( + 56 => wire__crate__api__wallet__Wallet_prepare_melt_impl(port, ptr, rust_vec_len, data_len), + 57 => wire__crate__api__wallet__Wallet_prepare_send_impl(port, ptr, rust_vec_len, data_len), + 58 => wire__crate__api__wallet__Wallet_receive_impl(port, ptr, rust_vec_len, data_len), + 59 => wire__crate__api__wallet__Wallet_reclaim_pending_proofs_impl( port, ptr, rust_vec_len, data_len, ), - 58 => wire__crate__api__wallet__Wallet_recover_incomplete_sagas_impl( + 60 => wire__crate__api__wallet__Wallet_reclaim_proofs_by_ys_impl( port, ptr, rust_vec_len, data_len, ), - 59 => { + 61 => wire__crate__api__wallet__Wallet_recover_incomplete_sagas_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 62 => { wire__crate__api__wallet__Wallet_refresh_balance_impl(port, ptr, rust_vec_len, data_len) } - 60 => wire__crate__api__wallet__Wallet_restore_impl(port, ptr, rust_vec_len, data_len), - 61 => wire__crate__api__wallet__Wallet_send_impl(port, ptr, rust_vec_len, data_len), - 62 => { + 63 => wire__crate__api__wallet__Wallet_restore_impl(port, ptr, rust_vec_len, data_len), + 64 => wire__crate__api__wallet__Wallet_send_impl(port, ptr, rust_vec_len, data_len), + 65 => { wire__crate__api__wallet__Wallet_stream_balance_impl(port, ptr, rust_vec_len, data_len) } - 63 => wire__crate__api__wallet__Wallet_wait_for_nostr_payment_impl( + 66 => wire__crate__api__wallet__Wallet_wait_for_nostr_payment_impl( port, ptr, rust_vec_len, data_len, ), - 66 => wire__crate__api__mint_info__fetch_keysets_impl(port, ptr, rust_vec_len, data_len), - 68 => wire__crate__api__mint_info__get_mint_info_impl(port, ptr, rust_vec_len, data_len), - 72 => wire__crate__api__mint_info__ping_mint_impl(port, ptr, rust_vec_len, data_len), - 73 => wire__crate__api__wallet__receive_options_default_impl( + 69 => wire__crate__api__mint_info__fetch_keysets_impl(port, ptr, rust_vec_len, data_len), + 71 => wire__crate__api__mint_info__get_mint_info_impl(port, ptr, rust_vec_len, data_len), + 75 => wire__crate__api__mint_info__ping_mint_impl(port, ptr, rust_vec_len, data_len), + 76 => wire__crate__api__wallet__receive_options_default_impl( port, ptr, rust_vec_len, data_len, ), - 74 => { + 77 => { wire__crate__api__wallet__send_options_default_impl(port, ptr, rust_vec_len, data_len) } _ => unreachable!(), @@ -4969,15 +5176,15 @@ fn pde_ffi_dispatcher_sync_impl( 34 => wire__crate__api__wallet__Wallet_auto_accessor_get_unit_impl(ptr, rust_vec_len, data_len), 35 => wire__crate__api__wallet__Wallet_auto_accessor_set_mint_url_impl(ptr, rust_vec_len, data_len), 36 => wire__crate__api__wallet__Wallet_auto_accessor_set_unit_impl(ptr, rust_vec_len, data_len), -51 => wire__crate__api__wallet__Wallet_new_impl(ptr, rust_vec_len, data_len), -64 => wire__crate__api__token__create_offline_token_impl(ptr, rust_vec_len, data_len), -65 => wire__crate__api__token__encode_qr_token_impl(ptr, rust_vec_len, data_len), -67 => wire__crate__api__keys__generate_mnemonic_impl(ptr, rust_vec_len, data_len), -69 => wire__crate__api__keys__get_pub_key_impl(ptr, rust_vec_len, data_len), -70 => wire__crate__api__keys__mnemonic_to_seed_impl(ptr, rust_vec_len, data_len), -71 => wire__crate__api__payment_request__payment_request_info_parse_impl(ptr, rust_vec_len, data_len), -75 => wire__crate__api__token__token_from_raw_bytes_impl(ptr, rust_vec_len, data_len), -76 => wire__crate__api__token__token_parse_impl(ptr, rust_vec_len, data_len), +54 => wire__crate__api__wallet__Wallet_new_impl(ptr, rust_vec_len, data_len), +67 => wire__crate__api__token__create_offline_token_impl(ptr, rust_vec_len, data_len), +68 => wire__crate__api__token__encode_qr_token_impl(ptr, rust_vec_len, data_len), +70 => wire__crate__api__keys__generate_mnemonic_impl(ptr, rust_vec_len, data_len), +72 => wire__crate__api__keys__get_pub_key_impl(ptr, rust_vec_len, data_len), +73 => wire__crate__api__keys__mnemonic_to_seed_impl(ptr, rust_vec_len, data_len), +74 => wire__crate__api__payment_request__payment_request_info_parse_impl(ptr, rust_vec_len, data_len), +78 => wire__crate__api__token__token_from_raw_bytes_impl(ptr, rust_vec_len, data_len), +79 => wire__crate__api__token__token_parse_impl(ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -6039,6 +6246,16 @@ impl SseEncode for Vec { } } +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + impl SseEncode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {