Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
faece2b
feat: Add 'restore-session' action and EmptyPayload
BraCR10 Nov 4, 2025
61f1e50
feat: Partially Implement restore session manager and progress state
BraCR10 Nov 4, 2025
8587759
feat: Create restore progress overlay UI and dialog
BraCR10 Nov 4, 2025
5828a05
fix: Update notification handling for restore action
BraCR10 Nov 4, 2025
21802bb
feat: Add localization for restore and import features
BraCR10 Nov 4, 2025
6aa579b
feat: Add 'orders' action enum and notification placeholders
BraCR10 Nov 5, 2025
6a86f74
feat: Introduce OrdersRequest and OrdersResponse data models
BraCR10 Nov 5, 2025
c623007
refactor(restore): Remove old RestoreMessage and rename restore method
BraCR10 Nov 5, 2025
98c9f93
refactor(restore): Setup event-driven infrastructure in RestoreService
BraCR10 Nov 5, 2025
3f56975
feat(restore): Implement staged restore process with order details fe…
BraCR10 Nov 5, 2025
0fad4e0
feat: Add LastTradeIndex action and response model
BraCR10 Nov 6, 2025
b91c40f
chore: Update notification message mapping for new actions
BraCR10 Nov 6, 2025
2740856
refactor(restore): Update dependencies and cleanup logic in RestoreSe…
BraCR10 Nov 6, 2025
2006194
feat(restore): Implement session-based restore and last trade index r…
BraCR10 Nov 6, 2025
33d1c27
feat(restore): Orchestrate new restore process flow
BraCR10 Nov 6, 2025
8fce8ba
chore: Clear notifications on master key regeneration
BraCR10 Nov 6, 2025
d3f9960
feat(restore) : Manage index trade resquest and show overley
BraCR10 Nov 6, 2025
b932715
feat : build msg logic
BraCR10 Nov 9, 2025
54db811
feat : disputes restore management
BraCR10 Nov 9, 2025
68e4565
feat : mnemonic checksum validator
BraCR10 Nov 10, 2025
b619b2b
refactor(restore): improve logs and comments
BraCR10 Nov 10, 2025
bd2b140
Refactors restore mode implementation
BraCR10 Nov 10, 2025
e254926
fix : solving action issues according with user roles
BraCR10 Nov 10, 2025
3c59a29
fix : removing duplicated var
BraCR10 Nov 10, 2025
cf3f61a
fix : restore msgs state issues
BraCR10 Nov 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/core/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:mostro_mobile/shared/providers/app_init_provider.dart';
import 'package:mostro_mobile/features/settings/settings_provider.dart';
import 'package:mostro_mobile/shared/notifiers/locale_notifier.dart';
import 'package:mostro_mobile/features/walkthrough/providers/first_run_provider.dart';
import 'package:mostro_mobile/features/restore/restore_overlay.dart';

class MostroApp extends ConsumerStatefulWidget {
const MostroApp({super.key});
Expand Down Expand Up @@ -163,6 +164,14 @@ class _MostroAppState extends ConsumerState<MostroApp> {
theme: AppTheme.theme,
darkTheme: AppTheme.theme,
routerConfig: _router!,
builder: (context, child) {
return Stack(
children: [
if (child != null) child,
const RestoreOverlay(),
],
);
},
// Use language override from settings if available, otherwise let callback handle detection
locale: settings.selectedLanguage != null
? Locale(settings.selectedLanguage!)
Expand Down
5 changes: 4 additions & 1 deletion lib/data/models/enums/action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ enum Action {
paymentFailed('payment-failed'),
invoiceUpdated('invoice-updated'),
sendDm('send-dm'),
tradePubkey('trade-pubkey');
tradePubkey('trade-pubkey'),
restore('restore-session'),
orders('orders'),
lastTradeIndex('last-trade-index');

final String value;

Expand Down
21 changes: 21 additions & 0 deletions lib/data/models/last_trade_index_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:mostro_mobile/data/models/payload.dart';

class LastTradeIndexResponse implements Payload {
final int tradeIndex;

const LastTradeIndexResponse({required this.tradeIndex});

@override
String get type => 'last-trade-index';

factory LastTradeIndexResponse.fromJson(Map<String, dynamic> json) {
return LastTradeIndexResponse(
tradeIndex: json['trade_index'] as int,
);
}

@override
Map<String, dynamic> toJson() => {
'trade_index': tradeIndex,
};
}
14 changes: 10 additions & 4 deletions lib/data/models/mostro_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ class MostroMessage<T extends Payload> {
json['id'] = id;
}
json['action'] = action.value;
json['payload'] = _payload?.toJson();
// Serialize EmptyPayload as null to match protocol specification
json['payload'] = (_payload is EmptyPayload) ? null :_payload?.toJson();
return json;
}

factory MostroMessage.fromJson(Map<String, dynamic> json) {
final timestamp = json['timestamp'];
json = json['order'] ?? json['cant-do'] ?? json;
// IMPORTANT : Use 'order', 'restore' or 'cant-do' key as per protocol
json = json['order'] ?? json['restore'] ?? json['cant-do'] ?? json;
final num requestId = json['request_id'] ?? 0;

return MostroMessage(
Expand Down Expand Up @@ -97,7 +99,9 @@ class MostroMessage<T extends Payload> {
}

String sign(NostrKeyPairs keyPair) {
final message = {'order': toJson()};
//IMPORTANT : Use 'restore' key for restore and last-trade-index actions, 'order' for everything else, as per protocol
final wrapperKey = action == Action.restore || action == Action.lastTradeIndex ? 'restore' : 'order';
final message = {wrapperKey: toJson()};
final serializedEvent = jsonEncode(message);
final bytes = utf8.encode(serializedEvent);
final digest = sha256.convert(bytes);
Expand All @@ -107,7 +111,9 @@ class MostroMessage<T extends Payload> {
}

String serialize({NostrKeyPairs? keyPair}) {
final message = {'order': toJson()};
//IMPORTANT : Use 'restore' key for restore and last-trade-index actions, 'order' for everything else, as per protocol
final wrapperKey = action == Action.restore || action == Action.lastTradeIndex ? 'restore' : 'order';
final message = {wrapperKey: toJson()};
final serializedEvent = jsonEncode(message);
final signature = (keyPair != null) ? '"${sign(keyPair)}"' : null;
final content = '[$serializedEvent, $signature]';
Expand Down
21 changes: 21 additions & 0 deletions lib/data/models/orders_request.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:mostro_mobile/data/models/payload.dart';

class OrdersPayload implements Payload {
final List<String> ids;

const OrdersPayload({required this.ids});

@override
String get type => 'orders';

factory OrdersPayload.fromJson(Map<String, dynamic> json) {
return OrdersPayload(
ids: (json['ids'] as List<dynamic>).map((e) => e as String).toList(),
);
}

@override
Map<String, dynamic> toJson() => {
'ids': ids,
};
}
95 changes: 95 additions & 0 deletions lib/data/models/orders_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import 'package:mostro_mobile/data/models/payload.dart';


class OrdersResponse implements Payload {
final List<OrderDetail> orders;

OrdersResponse({required this.orders});

@override
String get type => 'orders';

factory OrdersResponse.fromJson(Map<String, dynamic> json) {
return OrdersResponse(
orders: (json['orders'] as List<dynamic>?)
?.map((o) => OrderDetail.fromJson(o as Map<String, dynamic>))
.toList() ??
[],
);
}

@override
Map<String, dynamic> toJson() => {
'orders': orders.map((o) => o.toJson()).toList(),
};
}

class OrderDetail {
final String id;
final String kind;
final String status;
final int amount;
final String fiatCode;
final int? minAmount;
final int? maxAmount;
final int fiatAmount;
final String paymentMethod;
final int premium;
final String? buyerTradePubkey;
final String? sellerTradePubkey;
final int? createdAt;
final int? expiresAt;

OrderDetail({
required this.id,
required this.kind,
required this.status,
required this.amount,
required this.fiatCode,
this.minAmount,
this.maxAmount,
required this.fiatAmount,
required this.paymentMethod,
required this.premium,
this.buyerTradePubkey,
this.sellerTradePubkey,
this.createdAt,
this.expiresAt,
});

factory OrderDetail.fromJson(Map<String, dynamic> json) {
return OrderDetail(
id: json['id'] as String,
kind: json['kind'] as String,
status: json['status'] as String,
amount: json['amount'] as int,
fiatCode: json['fiat_code'] as String,
minAmount: json['min_amount'] != null ? json['min_amount'] as int : null,
maxAmount: json['max_amount'] != null ? json['max_amount'] as int : null,
fiatAmount: json['fiat_amount'] as int,
paymentMethod: json['payment_method'] as String,
premium: json['premium'] as int,
buyerTradePubkey: json['buyer_trade_pubkey'] != null ? json['buyer_trade_pubkey'] as String : null,
sellerTradePubkey: json['seller_trade_pubkey'] != null ? json['seller_trade_pubkey'] as String : null,
createdAt: json['created_at'] != null ? json['created_at'] as int : null,
expiresAt: json['expires_at'] != null ? json['expires_at'] as int : null,
);
}

Map<String, dynamic> toJson() => {
'id': id,
'kind': kind,
'status': status,
'amount': amount,
'fiat_code': fiatCode,
'min_amount': minAmount,
'max_amount': maxAmount,
'fiat_amount': fiatAmount,
'payment_method': paymentMethod,
'premium': premium,
'buyer_trade_pubkey': buyerTradePubkey,
'seller_trade_pubkey': sellerTradePubkey,
'created_at': createdAt,
'expires_at': expiresAt,
};
}
11 changes: 11 additions & 0 deletions lib/data/models/payload.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,14 @@ abstract class Payload {
}
}
}

/// Empty payload for actions that don't require payload data
class EmptyPayload implements Payload {
const EmptyPayload();

@override
String get type => 'empty';

@override
Map<String, dynamic> toJson() => {};
}
91 changes: 91 additions & 0 deletions lib/data/models/restore_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import 'package:mostro_mobile/data/models/payload.dart';

class RestoreData implements Payload {
final List<RestoredOrder> orders;
final List<RestoredDispute> disputes;

RestoreData({
required this.orders,
required this.disputes,
});

@override
String get type => 'restore_data';

factory RestoreData.fromJson(Map<String, dynamic> json) {
final restoreData = json['restore_data'] as Map<String, dynamic>;

return RestoreData(
orders: (restoreData['orders'] as List<dynamic>?)
?.map((o) => RestoredOrder.fromJson(o as Map<String, dynamic>))
.toList() ?? [],
disputes: (restoreData['disputes'] as List<dynamic>?)
?.map((d) => RestoredDispute.fromJson(d as Map<String, dynamic>))
.toList() ?? [],
);
}

@override
Map<String, dynamic> toJson() => {
'restore_data': {
'orders': orders.map((o) => o.toJson()).toList(),
'disputes': disputes.map((d) => d.toJson()).toList(),
}
};
}

class RestoredOrder {
final String id;
final int tradeIndex;
final String status;

RestoredOrder({
required this.id,
required this.tradeIndex,
required this.status,
});

factory RestoredOrder.fromJson(Map<String, dynamic> json) {
return RestoredOrder(
id: json['order_id'] as String,
tradeIndex: json['trade_index'] as int,
status: json['status'] as String,
);
}

Map<String, dynamic> toJson() => {
'order_id': id,
'trade_index': tradeIndex,
'status': status,
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

class RestoredDispute {
final String disputeId;
final String orderId;
final int tradeIndex;
final String status;

RestoredDispute({
required this.disputeId,
required this.orderId,
required this.tradeIndex,
required this.status,
});

factory RestoredDispute.fromJson(Map<String, dynamic> json) {
return RestoredDispute(
disputeId: json['dispute_id'] as String,
orderId: json['order_id'] as String,
tradeIndex: json['trade_index'] as int,
status: json['status'] as String,
);
}

Map<String, dynamic> toJson() => {
'dispute_id': disputeId,
'order_id': orderId,
'trade_index': tradeIndex,
'status': status,
};
}
7 changes: 7 additions & 0 deletions lib/data/repositories/open_orders_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,11 @@ class OpenOrdersRepository implements OrderRepository<NostrEvent> {
_subscribeToOrders();
_emitEvents();
}

/// Clear in-memory order cache and reload from relays (used during account restore)
void clearCache() {
_logger.i('Clearing order cache and reloading');
_events.clear();
_subscribeToOrders(); // Resubscribe to reload orders from relays
}
}
Loading