Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 10 additions & 6 deletions lib/core/config.dart
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
import 'package:flutter/foundation.dart';

class Config {
// Configuración de Nostr
// Nostr configuration
static const List<String> nostrRelays = [
'wss://relay.mostro.network',
//'ws://127.0.0.1:7000',
//'ws://192.168.1.103:7000',
//'ws://10.0.2.2:7000', // mobile emulator
];

// hexkey de Mostro
// Mostro hexkey
static const String mostroPubKey =
'82fa8cb978b43c79b2156585bac2c011176a21d2aead6d9f7c575c005be88390';
//'9d9d0455a96871f2dc4289b8312429db2e925f167b37c77bf7b28014be235980';

static const String dBName = 'mostro.db';
static const String dBPassword = 'mostro';

// Tiempo de espera para conexiones a relays
// Timeout for relay connections
static const Duration nostrConnectionTimeout = Duration(seconds: 30);

static bool fullPrivacyMode = false;

// Modo de depuración
// Debug mode
static bool get isDebug => !kReleaseMode;

// Versión de Mostro
// Mostro version
static int mostroVersion = 1;

static int expirationSeconds = 900;
static int expirationHours = 24;

// Configuración de notificaciones
// Notification configuration
static String notificationChannelId = 'mostro_mobile';
static int notificationId = 38383;

// Timeouts for timeout detection (new critical operations)
static const Duration timeoutDetectionTimeout = Duration(seconds: 8);
static const Duration messageStorageTimeout = Duration(seconds: 5);
}
3 changes: 2 additions & 1 deletion lib/data/models/enums/action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ enum Action {
paymentFailed('payment-failed'),
invoiceUpdated('invoice-updated'),
sendDm('send-dm'),
tradePubkey('trade-pubkey');
tradePubkey('trade-pubkey'),
timeoutReversal('timeout-reversal');

final String value;

Expand Down
55 changes: 55 additions & 0 deletions lib/data/models/mostro_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import 'package:dart_nostr/nostr/core/key_pairs.dart';
import 'package:dart_nostr/nostr/model/event/event.dart';
import 'package:mostro_mobile/core/config.dart';
import 'package:mostro_mobile/data/models/enums/action.dart';
import 'package:mostro_mobile/data/models/enums/order_type.dart';
import 'package:mostro_mobile/data/models/enums/status.dart';
import 'package:mostro_mobile/data/models/nostr_event.dart';
import 'package:mostro_mobile/data/models/order.dart';
import 'package:mostro_mobile/data/models/payload.dart';
import 'package:mostro_mobile/shared/utils/nostr_utils.dart';

Expand All @@ -25,6 +29,57 @@ class MostroMessage<T extends Payload> {
this.timestamp})
: _payload = payload;

/// Factory constructor for creating timeout reversal messages
/// This creates a synthetic message to persist the timeout state change
/// with complete order information from the public event
factory MostroMessage.createTimeoutReversal({
required String orderId,
required int timestamp,
required Status originalStatus,
required NostrEvent publicEvent,
}) {
// Extract complete information from the 38383 public event
final fiatAmountRange = publicEvent.fiatAmount;
final paymentMethodsList = publicEvent.paymentMethods;

return MostroMessage(
action: Action.timeoutReversal,
id: orderId,
timestamp: timestamp,
payload: Order(
// Core order information from public event
id: publicEvent.orderId,
status: Status.pending, // Only this changes - reverting to pending
kind: publicEvent.orderType ?? OrderType.sell,
fiatCode: publicEvent.currency ?? 'USD',
fiatAmount: fiatAmountRange.minimum,
paymentMethod: paymentMethodsList.isNotEmpty
? paymentMethodsList.join(', ')
: 'N/A',

// Additional information for proper UI display
amount: int.tryParse(publicEvent.amount ?? '0') ?? 0,
minAmount: fiatAmountRange.minimum,
maxAmount: fiatAmountRange.maximum,
premium: int.tryParse(publicEvent.premium ?? '0') ?? 0,

// Timestamps for countdown timer and creation info
createdAt: publicEvent.createdAt?.millisecondsSinceEpoch,
expiresAt: publicEvent.expirationDate.millisecondsSinceEpoch,

// Master keys for reputation display (may be null, that's OK)
masterBuyerPubkey: publicEvent.orderType == OrderType.buy
? publicEvent.pubkey // Creator of buy order is buyer
: null,
masterSellerPubkey: publicEvent.orderType == OrderType.sell
? publicEvent.pubkey // Creator of sell order is seller
: null,

// Trade keys and other fields will be null, which is fine for UI
) as T,
);
}

Map<String, dynamic> toJson() {
Map<String, dynamic> json = {
'version': Config.mostroVersion,
Expand Down
24 changes: 17 additions & 7 deletions lib/features/order/models/order_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class OrderState {
}

OrderState updateWith(MostroMessage message) {
_logger.i('🔄 Updating OrderState with Action: ${message.action}');
_logger.i('Updating OrderState with Action: ${message.action}');

// Preserve the current state entirely for cantDo messages - they are informational only
if (message.action == Action.cantDo) {
Expand All @@ -93,14 +93,14 @@ class OrderState {
Status newStatus = _getStatusFromAction(
message.action, message.getPayload<Order>()?.status);

// 🔍 DEBUG: Log status mapping
_logger.i('📊 Status mapping: ${message.action} → $newStatus');
// DEBUG: Log status mapping
_logger.i('Status mapping: ${message.action} → $newStatus');

// Preserve PaymentRequest correctly
PaymentRequest? newPaymentRequest;
if (message.payload is PaymentRequest) {
newPaymentRequest = message.getPayload<PaymentRequest>();
_logger.i('💳 New PaymentRequest found in message');
_logger.i('New PaymentRequest found in message');
} else {
newPaymentRequest = paymentRequest; // Preserve existing
}
Expand All @@ -119,9 +119,9 @@ class OrderState {
peer: message.getPayload<Peer>() ?? peer,
);

_logger.i('New state: ${newState.status} - ${newState.action}');
_logger.i('New state: ${newState.status} - ${newState.action}');
_logger
.i('💳 PaymentRequest preserved: ${newState.paymentRequest != null}');
.i('PaymentRequest preserved: ${newState.paymentRequest != null}');

return newState;
}
Expand All @@ -139,7 +139,7 @@ class OrderState {
case Action.addInvoice:
return Status.waitingBuyerInvoice;

// FIX: Cuando alguien toma una orden, debe cambiar el status inmediatamente
// FIX: Cuando alguien toma una orden, debe cambiar el status inmediatamente
case Action.takeBuy:
// Cuando buyer toma sell order, seller debe esperar buyer invoice
return Status.waitingBuyerInvoice;
Expand Down Expand Up @@ -176,6 +176,10 @@ class OrderState {
case Action.newOrder:
return payloadStatus ?? status;

// Action for timeout reversal - always use payload status (should be pending)
case Action.timeoutReversal:
return payloadStatus ?? Status.pending;

// For other actions, keep the current status unless payload has a different one
default:
return payloadStatus ?? status;
Expand All @@ -195,6 +199,9 @@ class OrderState {
Action.takeBuy: [
Action.cancel,
],
Action.timeoutReversal: [
Action.cancel,
],
},
Status.waitingPayment: {
Action.payInvoice: [
Expand Down Expand Up @@ -272,6 +279,9 @@ class OrderState {
Action.takeSell: [
Action.cancel,
],
Action.timeoutReversal: [
Action.cancel,
],
},
Status.waitingPayment: {
Action.waitingSellerToPay: [
Expand Down
3 changes: 3 additions & 0 deletions lib/features/order/notfiers/abstract_mostro_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ class AbstractMostroNotifier extends StateNotifier<OrderState> {
'payment_retries_interval': -1000
});
break;
case Action.timeoutReversal:
// No automatic notification - handled manually in OrderNotifier
break;
default:
notifProvider.showInformation(event.action, values: {});
break;
Expand Down
Loading