From 4913986813ea54a4a5c86e46bb1a62e72f40e2ab Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Wed, 3 Sep 2025 16:34:37 -0300 Subject: [PATCH 1/9] feat: implement dispute creation with NIP-44 encrypted messaging --- lib/data/repositories/dispute_repository.dart | 85 ++++++++++++++++++- .../disputes/providers/dispute_providers.dart | 12 +++ .../trades/screens/trade_detail_screen.dart | 35 +++++++- lib/l10n/intl_en.arb | 10 +++ lib/l10n/intl_es.arb | 10 +++ lib/l10n/intl_it.arb | 10 +++ lib/services/dispute_service.dart | 38 +++++++-- 7 files changed, 189 insertions(+), 11 deletions(-) diff --git a/lib/data/repositories/dispute_repository.dart b/lib/data/repositories/dispute_repository.dart index 9c186b01..c477cf47 100644 --- a/lib/data/repositories/dispute_repository.dart +++ b/lib/data/repositories/dispute_repository.dart @@ -1,10 +1,87 @@ +import 'dart:convert'; +import 'package:collection/collection.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logger/logger.dart'; import 'package:mostro_mobile/data/models/dispute.dart'; +import 'package:mostro_mobile/services/nostr_service.dart'; +import 'package:mostro_mobile/shared/providers/session_notifier_provider.dart'; +import 'package:mostro_mobile/shared/utils/nostr_utils.dart'; -/// Stub repository for disputes - UI only implementation +/// Repository for managing dispute creation class DisputeRepository { - static final DisputeRepository _instance = DisputeRepository._internal(); - factory DisputeRepository() => _instance; - DisputeRepository._internal(); + final NostrService _nostrService; + final String _mostroPubkey; + final Ref _ref; + final Logger _logger = Logger(); + + DisputeRepository(this._nostrService, this._mostroPubkey, this._ref); + + /// Create a new dispute for an order + Future createDispute(String orderId) async { + try { + _logger.d('Creating dispute for order: $orderId'); + + // Get user's session for the order to get the trade key + final sessions = _ref.read(sessionNotifierProvider); + final session = sessions.firstWhereOrNull( + (s) => s.orderId == orderId, + ); + + if (session == null) { + _logger + .e('No session found for order: $orderId, cannot create dispute'); + return false; + } + + final privateKey = session.tradeKey.private; + if (privateKey.isEmpty) { + _logger.e('Session trade key private key is empty for order: $orderId'); + return false; + } + + // Create dispute message according to Mostro protocol + final disputeMessage = [ + { + 'order': { + 'version': 1, + 'id': orderId, + 'action': 'dispute', + 'payload': null, + } + }, + null // Signature placeholder + ]; + + // Convert the dispute message to JSON string + final messageJson = jsonEncode(disputeMessage); + + // Encrypt the message using NIP-44 encryption + final encryptedContent = await NostrUtils.encryptNIP44( + messageJson, + privateKey, + _mostroPubkey + ); + + // Create and sign the Nostr event using NostrUtils with encrypted content + final signedEvent = NostrUtils.createEvent( + kind: 4, // Direct message kind + content: encryptedContent, + privateKey: privateKey, + tags: [ + ['p', _mostroPubkey], // Send to Mostro + ], + ); + + // Send the encrypted event to Mostro + await _nostrService.publishEvent(signedEvent); + + _logger.d('Successfully sent dispute creation for order: $orderId'); + return true; + } catch (e) { + _logger.e('Failed to create dispute: $e'); + return false; + } + } Future> getUserDisputes() async { // Mock implementation for UI testing diff --git a/lib/features/disputes/providers/dispute_providers.dart b/lib/features/disputes/providers/dispute_providers.dart index 2ab4a577..0412a77a 100644 --- a/lib/features/disputes/providers/dispute_providers.dart +++ b/lib/features/disputes/providers/dispute_providers.dart @@ -1,8 +1,20 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mostro_mobile/data/models/dispute.dart'; import 'package:mostro_mobile/data/models/dispute_chat.dart'; +import 'package:mostro_mobile/data/repositories/dispute_repository.dart'; import 'package:mostro_mobile/features/disputes/notifiers/dispute_chat_notifier.dart'; import 'package:mostro_mobile/features/disputes/data/dispute_mock_data.dart'; +import 'package:mostro_mobile/shared/providers/nostr_service_provider.dart'; +import 'package:mostro_mobile/features/settings/settings_provider.dart'; + +/// Provider for the dispute repository +final disputeRepositoryProvider = Provider.autoDispose((ref) { + final nostrService = ref.watch(nostrServiceProvider); + final settings = ref.watch(settingsProvider); + final mostroPubkey = settings.mostroPublicKey; + + return DisputeRepository(nostrService, mostroPubkey, ref); +}); /// Provider for dispute details - uses mock data when enabled final disputeDetailsProvider = FutureProvider.family((ref, disputeId) async { diff --git a/lib/features/trades/screens/trade_detail_screen.dart b/lib/features/trades/screens/trade_detail_screen.dart index 88bb4e34..4724b5c3 100644 --- a/lib/features/trades/screens/trade_detail_screen.dart +++ b/lib/features/trades/screens/trade_detail_screen.dart @@ -23,6 +23,7 @@ import 'package:mostro_mobile/features/mostro/mostro_instance.dart'; import 'package:mostro_mobile/shared/providers/mostro_storage_provider.dart'; import 'package:mostro_mobile/data/models/mostro_message.dart'; import 'package:mostro_mobile/shared/providers/time_provider.dart'; +import 'package:mostro_mobile/features/disputes/providers/dispute_providers.dart'; import 'package:mostro_mobile/generated/l10n.dart'; class TradeDetailScreen extends ConsumerWidget { @@ -711,7 +712,39 @@ class TradeDetailScreen extends ConsumerWidget { // Only proceed with dispute if user confirmed if (result == true) { - ref.read(orderNotifierProvider(orderId).notifier).disputeOrder(); + try { + // Create dispute using the repository + final repository = ref.read(disputeRepositoryProvider); + final success = await repository.createDispute(orderId); + + if (success && context.mounted) { + // Also notify the order notifier + ref.read(orderNotifierProvider(orderId).notifier).disputeOrder(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(S.of(context)!.disputeCreatedSuccessfully), + backgroundColor: AppTheme.mostroGreen, + ), + ); + } else if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(S.of(context)!.disputeCreationFailed), + backgroundColor: AppTheme.red1, + ), + ); + } + } catch (e) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(S.of(context)!.disputeCreationError(e.toString())), + backgroundColor: AppTheme.red1, + ), + ); + } + } } }, style: ElevatedButton.styleFrom( diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index e18f1951..4cec19a8 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -267,6 +267,16 @@ "orderIdCopiedMessage": "Order ID copied to clipboard", "disputeTradeDialogTitle": "Start Dispute", "disputeTradeDialogContent": "You are about to start a dispute with your counterparty. Do you want to continue?", + "disputeCreatedSuccessfully": "Dispute created successfully", + "disputeCreationFailed": "Failed to create dispute", + "disputeCreationError": "Error creating dispute: {error}", + "@disputeCreationError": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "typeToAdd": "Type to add...", "noneSelected": "None selected", "fiatCurrencies": "Fiat currencies", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 03b12403..c8654216 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -608,6 +608,16 @@ "orderIdCopiedMessage": "ID de orden copiado al portapapeles", "disputeTradeDialogTitle": "Iniciar Disputa", "disputeTradeDialogContent": "Estás a punto de iniciar una disputa con tu contraparte. ¿Deseas continuar?", + "disputeCreatedSuccessfully": "Disputa creada exitosamente", + "disputeCreationFailed": "Falló la creación de la disputa", + "disputeCreationError": "Error al crear la disputa: {error}", + "@disputeCreationError": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "language": "Idioma", "systemDefault": "Predeterminado", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 3ff6f76c..f57602aa 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -649,6 +649,16 @@ "orderIdCopiedMessage": "ID ordine copiato negli appunti", "disputeTradeDialogTitle": "Inizia Disputa", "disputeTradeDialogContent": "Stai per iniziare una disputa con la tua controparte. Vuoi continuare?", + "disputeCreatedSuccessfully": "Disputa creata con successo", + "disputeCreationFailed": "Creazione della disputa fallita", + "disputeCreationError": "Errore nella creazione della disputa: {error}", + "@disputeCreationError": { + "placeholders": { + "error": { + "type": "String" + } + } + }, "language": "Lingua", "systemDefault": "Predefinito di sistema", diff --git a/lib/services/dispute_service.dart b/lib/services/dispute_service.dart index f4eb7e20..d7c5a34c 100644 --- a/lib/services/dispute_service.dart +++ b/lib/services/dispute_service.dart @@ -1,5 +1,4 @@ import 'package:mostro_mobile/data/models/dispute.dart'; -import 'package:mostro_mobile/data/repositories/dispute_repository.dart'; /// Stub service for disputes - UI only implementation class DisputeService { @@ -7,18 +6,45 @@ class DisputeService { factory DisputeService() => _instance; DisputeService._internal(); - final DisputeRepository _disputeRepository = DisputeRepository(); - Future> getUserDisputes() async { - return await _disputeRepository.getUserDisputes(); + // Mock implementation for UI testing + await Future.delayed(const Duration(milliseconds: 500)); + + return [ + Dispute( + disputeId: 'dispute_001', + orderId: 'order_001', + status: 'initiated', + createdAt: DateTime.now().subtract(const Duration(hours: 1)), + action: 'dispute-initiated-by-you', + ), + Dispute( + disputeId: 'dispute_002', + orderId: 'order_002', + status: 'in-progress', + createdAt: DateTime.now().subtract(const Duration(days: 1)), + action: 'dispute-initiated-by-peer', + adminPubkey: 'admin_123', + ), + ]; } Future getDispute(String disputeId) async { - return await _disputeRepository.getDispute(disputeId); + // Mock implementation + await Future.delayed(const Duration(milliseconds: 300)); + + return Dispute( + disputeId: disputeId, + orderId: 'order_${disputeId.substring(0, 8)}', + status: 'initiated', + createdAt: DateTime.now().subtract(const Duration(hours: 2)), + action: 'dispute-initiated-by-you', + ); } Future sendDisputeMessage(String disputeId, String message) async { - await _disputeRepository.sendDisputeMessage(disputeId, message); + // Mock implementation + await Future.delayed(const Duration(milliseconds: 200)); } Future initiateDispute(String orderId, String reason) async { From 4dfe38ced49660e356c0d34de6e870ebb06e9ef1 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Wed, 3 Sep 2025 16:58:45 -0300 Subject: [PATCH 2/9] refactor: simplify dispute creation using MostroMessage wrapper and NIP-17 protocol --- lib/data/repositories/dispute_repository.dart | 51 +++++-------------- .../trades/screens/trade_detail_screen.dart | 2 +- lib/l10n/intl_en.arb | 4 +- lib/l10n/intl_es.arb | 4 +- lib/l10n/intl_it.arb | 4 +- 5 files changed, 19 insertions(+), 46 deletions(-) diff --git a/lib/data/repositories/dispute_repository.dart b/lib/data/repositories/dispute_repository.dart index c477cf47..6013b34d 100644 --- a/lib/data/repositories/dispute_repository.dart +++ b/lib/data/repositories/dispute_repository.dart @@ -1,11 +1,11 @@ -import 'dart:convert'; import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logger/logger.dart'; import 'package:mostro_mobile/data/models/dispute.dart'; +import 'package:mostro_mobile/data/models/mostro_message.dart'; +import 'package:mostro_mobile/data/models/enums/action.dart'; import 'package:mostro_mobile/services/nostr_service.dart'; import 'package:mostro_mobile/shared/providers/session_notifier_provider.dart'; -import 'package:mostro_mobile/shared/utils/nostr_utils.dart'; /// Repository for managing dispute creation class DisputeRepository { @@ -33,47 +33,20 @@ class DisputeRepository { return false; } - final privateKey = session.tradeKey.private; - if (privateKey.isEmpty) { - _logger.e('Session trade key private key is empty for order: $orderId'); - return false; - } - - // Create dispute message according to Mostro protocol - final disputeMessage = [ - { - 'order': { - 'version': 1, - 'id': orderId, - 'action': 'dispute', - 'payload': null, - } - }, - null // Signature placeholder - ]; - - // Convert the dispute message to JSON string - final messageJson = jsonEncode(disputeMessage); - - // Encrypt the message using NIP-44 encryption - final encryptedContent = await NostrUtils.encryptNIP44( - messageJson, - privateKey, - _mostroPubkey + // Create dispute message using Gift Wrap protocol (NIP-17) + final disputeMessage = MostroMessage( + action: Action.dispute, + id: orderId, ); - // Create and sign the Nostr event using NostrUtils with encrypted content - final signedEvent = NostrUtils.createEvent( - kind: 4, // Direct message kind - content: encryptedContent, - privateKey: privateKey, - tags: [ - ['p', _mostroPubkey], // Send to Mostro - ], + // Wrap message using NIP-17 Gift Wrap protocol + final event = await disputeMessage.wrap( + tradeKey: session.tradeKey, + recipientPubKey: _mostroPubkey, ); - // Send the encrypted event to Mostro - await _nostrService.publishEvent(signedEvent); + // Send the wrapped event to Mostro + await _nostrService.publishEvent(event); _logger.d('Successfully sent dispute creation for order: $orderId'); return true; diff --git a/lib/features/trades/screens/trade_detail_screen.dart b/lib/features/trades/screens/trade_detail_screen.dart index 4724b5c3..e4d884ca 100644 --- a/lib/features/trades/screens/trade_detail_screen.dart +++ b/lib/features/trades/screens/trade_detail_screen.dart @@ -739,7 +739,7 @@ class TradeDetailScreen extends ConsumerWidget { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(S.of(context)!.disputeCreationError(e.toString())), + content: Text(S.of(context)!.disputeCreationErrorWithMessage(e.toString())), backgroundColor: AppTheme.red1, ), ); diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 4cec19a8..0d2601bc 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -269,8 +269,8 @@ "disputeTradeDialogContent": "You are about to start a dispute with your counterparty. Do you want to continue?", "disputeCreatedSuccessfully": "Dispute created successfully", "disputeCreationFailed": "Failed to create dispute", - "disputeCreationError": "Error creating dispute: {error}", - "@disputeCreationError": { + "disputeCreationErrorWithMessage": "Error creating dispute: {error}", + "@disputeCreationErrorWithMessage": { "placeholders": { "error": { "type": "String" diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index c8654216..f1379123 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -610,8 +610,8 @@ "disputeTradeDialogContent": "Estás a punto de iniciar una disputa con tu contraparte. ¿Deseas continuar?", "disputeCreatedSuccessfully": "Disputa creada exitosamente", "disputeCreationFailed": "Falló la creación de la disputa", - "disputeCreationError": "Error al crear la disputa: {error}", - "@disputeCreationError": { + "disputeCreationErrorWithMessage": "Error al crear la disputa: {error}", + "@disputeCreationErrorWithMessage": { "placeholders": { "error": { "type": "String" diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index f57602aa..1462ba0a 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -651,8 +651,8 @@ "disputeTradeDialogContent": "Stai per iniziare una disputa con la tua controparte. Vuoi continuare?", "disputeCreatedSuccessfully": "Disputa creata con successo", "disputeCreationFailed": "Creazione della disputa fallita", - "disputeCreationError": "Errore nella creazione della disputa: {error}", - "@disputeCreationError": { + "disputeCreationErrorWithMessage": "Errore nella creazione della disputa: {error}", + "@disputeCreationErrorWithMessage": { "placeholders": { "error": { "type": "String" From 362c57f6905c5c1ad75185fd5abf4fafb1a93205 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Thu, 4 Sep 2025 01:36:30 -0300 Subject: [PATCH 3/9] fix: remove redundant order notifier update when creating dispute --- lib/features/trades/screens/trade_detail_screen.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/features/trades/screens/trade_detail_screen.dart b/lib/features/trades/screens/trade_detail_screen.dart index e4d884ca..0cd4a33a 100644 --- a/lib/features/trades/screens/trade_detail_screen.dart +++ b/lib/features/trades/screens/trade_detail_screen.dart @@ -718,9 +718,6 @@ class TradeDetailScreen extends ConsumerWidget { final success = await repository.createDispute(orderId); if (success && context.mounted) { - // Also notify the order notifier - ref.read(orderNotifierProvider(orderId).notifier).disputeOrder(); - ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(S.of(context)!.disputeCreatedSuccessfully), From 2b0bd0bdc043e43f292af98498ff08dd1bd7abff Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Thu, 4 Sep 2025 02:12:37 -0300 Subject: [PATCH 4/9] i18n: rename dispute token to dispute ID in English, Spanish and Italian translations --- lib/l10n/intl_en.arb | 6 +++--- lib/l10n/intl_es.arb | 6 +++--- lib/l10n/intl_it.arb | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 0d2601bc..2d95990e 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -106,10 +106,10 @@ "cooperativeCancelInitiatedByYou": "You have initiated the cancellation of the order ID: {id}. Your counterparty must agree. If they do not respond, you can open a dispute. Note that no administrator will contact you regarding this cancellation unless you open a dispute first.", "cooperativeCancelInitiatedByPeer": "Your counterparty wants to cancel order ID: {id}. If you agree, please send me cancel-order-message. Note that no administrator will contact you regarding this cancellation unless you open a dispute first.", "cooperativeCancelAccepted": "Order {id} has been successfully canceled!", - "disputeInitiatedByYou": "You have initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute token is: {user_token}.", - "disputeInitiatedByPeer": "Your counterparty has initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute token is: {user_token}.", + "disputeInitiatedByYou": "You have initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute ID is: {user_token}.", + "disputeInitiatedByPeer": "Your counterparty has initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute ID is: {user_token}.", "adminTookDisputeAdmin": "Here are the details of the dispute order you have taken: {details}. You need to determine which user is correct and decide whether to cancel or complete the order. Please note that your decision will be final and cannot be reversed.", - "adminTookDisputeUsers": "The solver {admin_npub} will handle your dispute. You can contact them directly, but if they reach out to you first, make sure to ask them for your dispute token.", + "adminTookDisputeUsers": "The solver {admin_npub} will handle your dispute. You can contact them directly, but if they reach out to you first, make sure to ask them for your dispute ID.", "adminCanceledAdmin": "You have canceled the order ID: {id}.", "adminCanceledUsers": "Admin has canceled the order ID: {id}.", "adminSettledAdmin": "You have completed the order ID: {id}.", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index f1379123..6bdb69ce 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -106,10 +106,10 @@ "cooperativeCancelInitiatedByYou": "Has iniciado la cancelación de la orden ID: {id}. Tu contraparte debe estar de acuerdo. Si no responden, puedes abrir una disputa. Ten en cuenta que ningún administrador te contactará sobre esta cancelación a menos que abras una disputa primero.", "cooperativeCancelInitiatedByPeer": "Tu contraparte quiere cancelar la orden ID: {id}. Si estás de acuerdo, por favor envíame cancel-order-message. Ten en cuenta que ningún administrador te contactará sobre esta cancelación a menos que abras una disputa primero.", "cooperativeCancelAccepted": "¡La orden {id} ha sido cancelada exitosamente!", - "disputeInitiatedByYou": "Has iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu token de disputa es: {user_token}.", - "disputeInitiatedByPeer": "Tu contraparte ha iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu token de disputa es: {user_token}.", + "disputeInitiatedByYou": "Has iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu ID de disputa es: {user_token}.", + "disputeInitiatedByPeer": "Tu contraparte ha iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu ID de disputa es: {user_token}.", "adminTookDisputeAdmin": "Aquí están los detalles de la orden en disputa que has tomado: {details}. Necesitas determinar qué usuario es correcto y decidir si cancelar o completar la orden. Ten en cuenta que tu decisión será final y no se puede revertir.", - "adminTookDisputeUsers": "El resolutor {admin_npub} manejará tu disputa. Puedes contactarlos directamente, pero si te contactan primero, asegúrate de pedirles tu token de disputa.", + "adminTookDisputeUsers": "El resolutor {admin_npub} manejará tu disputa. Puedes contactarlos directamente, pero si te contactan primero, asegúrate de pedirles tu ID de disputa.", "adminCanceledAdmin": "Has cancelado la orden ID: {id}.", "adminCanceledUsers": "El administrador ha cancelado la orden ID: {id}.", "adminSettledAdmin": "Has completado la orden ID: {id}.", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 1462ba0a..94e5c745 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -106,10 +106,10 @@ "cooperativeCancelInitiatedByYou": "Hai iniziato l'annullamento dell'ordine ID: {id}. La tua controparte deve anche concordare l'annullamento. Se non risponde, puoi aprire una disputa. Nota che nessun amministratore ti contatterà MAI riguardo questo annullamento a meno che tu non apra prima una disputa.", "cooperativeCancelInitiatedByPeer": "La tua controparte vuole annullare l'ordine ID: {id}. Nota che nessun amministratore ti contatterà MAI riguardo questo annullamento a meno che tu non apra prima una disputa. Se concordi su tale annullamento, premi: Annulla Ordine.", "cooperativeCancelAccepted": "L'ordine {id} è stato annullato con successo!", - "disputeInitiatedByYou": "Hai iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato presto alla tua disputa. Una volta assegnato, riceverai il suo npub e solo questo account potrà assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti il token per la tua disputa. Il token di questa disputa è: {user_token}.", - "disputeInitiatedByPeer": "La tua controparte ha iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato presto alla tua disputa. Una volta assegnato, ti condividerò il loro npub e solo loro potranno assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti il token per la tua disputa. Il token di questa disputa è: {user_token}.", + "disputeInitiatedByYou": "Hai iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato presto alla tua disputa. Una volta assegnato, riceverai il suo npub e solo questo account potrà assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa. L'ID di questa disputa è: {user_token}.", + "disputeInitiatedByPeer": "La tua controparte ha iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato presto alla tua disputa. Una volta assegnato, ti condividerò il loro npub e solo loro potranno assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa. L'ID di questa disputa è: {user_token}.", "adminTookDisputeAdmin": "Ecco i dettagli dell'ordine della disputa che hai preso: {details}. Devi determinare quale utente ha ragione e decidere se annullare o completare l'ordine. Nota che la tua decisione sarà finale e non può essere annullata.", - "adminTookDisputeUsers": "L'amministratore {admin_npub} gestirà la tua disputa. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti il token per la tua disputa..", + "adminTookDisputeUsers": "L'amministratore {admin_npub} gestirà la tua disputa. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa..", "adminCanceledAdmin": "Hai annullato l'ordine ID: {id}!", "adminCanceledUsers": "L'amministratore ha annullato l'ordine ID: {id}!", "adminSettledAdmin": "Hai completato l'ordine ID: {id}!", From 70d28a1174f070aeeb444043e9bd62b8f59307c6 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Thu, 4 Sep 2025 19:33:40 -0300 Subject: [PATCH 5/9] refactor: rename user_token to dispute_id and improve dispute ID handling --- lib/l10n/intl_en.arb | 6 +++--- lib/l10n/intl_es.arb | 6 +++--- lib/l10n/intl_it.arb | 6 +++--- lib/services/dispute_service.dart | 7 ++++++- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 2d95990e..3500bb76 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -106,10 +106,10 @@ "cooperativeCancelInitiatedByYou": "You have initiated the cancellation of the order ID: {id}. Your counterparty must agree. If they do not respond, you can open a dispute. Note that no administrator will contact you regarding this cancellation unless you open a dispute first.", "cooperativeCancelInitiatedByPeer": "Your counterparty wants to cancel order ID: {id}. If you agree, please send me cancel-order-message. Note that no administrator will contact you regarding this cancellation unless you open a dispute first.", "cooperativeCancelAccepted": "Order {id} has been successfully canceled!", - "disputeInitiatedByYou": "You have initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute ID is: {user_token}.", - "disputeInitiatedByPeer": "Your counterparty has initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute ID is: {user_token}.", + "disputeInitiatedByYou": "You have initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute ID is: {dispute_id}.", + "disputeInitiatedByPeer": "Your counterparty has initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute ID is: {dispute_id}.", "adminTookDisputeAdmin": "Here are the details of the dispute order you have taken: {details}. You need to determine which user is correct and decide whether to cancel or complete the order. Please note that your decision will be final and cannot be reversed.", - "adminTookDisputeUsers": "The solver {admin_npub} will handle your dispute. You can contact them directly, but if they reach out to you first, make sure to ask them for your dispute ID.", + "adminTookDisputeUsers": "The solver {admin_npub} will handle your dispute. You can contact them directly.", "adminCanceledAdmin": "You have canceled the order ID: {id}.", "adminCanceledUsers": "Admin has canceled the order ID: {id}.", "adminSettledAdmin": "You have completed the order ID: {id}.", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 6bdb69ce..1910cbce 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -106,10 +106,10 @@ "cooperativeCancelInitiatedByYou": "Has iniciado la cancelación de la orden ID: {id}. Tu contraparte debe estar de acuerdo. Si no responden, puedes abrir una disputa. Ten en cuenta que ningún administrador te contactará sobre esta cancelación a menos que abras una disputa primero.", "cooperativeCancelInitiatedByPeer": "Tu contraparte quiere cancelar la orden ID: {id}. Si estás de acuerdo, por favor envíame cancel-order-message. Ten en cuenta que ningún administrador te contactará sobre esta cancelación a menos que abras una disputa primero.", "cooperativeCancelAccepted": "¡La orden {id} ha sido cancelada exitosamente!", - "disputeInitiatedByYou": "Has iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu ID de disputa es: {user_token}.", - "disputeInitiatedByPeer": "Tu contraparte ha iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu ID de disputa es: {user_token}.", + "disputeInitiatedByYou": "Has iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu ID de disputa es: {dispute_id}.", + "disputeInitiatedByPeer": "Tu contraparte ha iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu ID de disputa es: {dispute_id}.", "adminTookDisputeAdmin": "Aquí están los detalles de la orden en disputa que has tomado: {details}. Necesitas determinar qué usuario es correcto y decidir si cancelar o completar la orden. Ten en cuenta que tu decisión será final y no se puede revertir.", - "adminTookDisputeUsers": "El resolutor {admin_npub} manejará tu disputa. Puedes contactarlos directamente, pero si te contactan primero, asegúrate de pedirles tu ID de disputa.", + "adminTookDisputeUsers": "El resolutor {admin_npub} manejará tu disputa. Puedes contactarlos directamente.", "adminCanceledAdmin": "Has cancelado la orden ID: {id}.", "adminCanceledUsers": "El administrador ha cancelado la orden ID: {id}.", "adminSettledAdmin": "Has completado la orden ID: {id}.", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 94e5c745..6a7a9325 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -106,10 +106,10 @@ "cooperativeCancelInitiatedByYou": "Hai iniziato l'annullamento dell'ordine ID: {id}. La tua controparte deve anche concordare l'annullamento. Se non risponde, puoi aprire una disputa. Nota che nessun amministratore ti contatterà MAI riguardo questo annullamento a meno che tu non apra prima una disputa.", "cooperativeCancelInitiatedByPeer": "La tua controparte vuole annullare l'ordine ID: {id}. Nota che nessun amministratore ti contatterà MAI riguardo questo annullamento a meno che tu non apra prima una disputa. Se concordi su tale annullamento, premi: Annulla Ordine.", "cooperativeCancelAccepted": "L'ordine {id} è stato annullato con successo!", - "disputeInitiatedByYou": "Hai iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato presto alla tua disputa. Una volta assegnato, riceverai il suo npub e solo questo account potrà assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa. L'ID di questa disputa è: {user_token}.", - "disputeInitiatedByPeer": "La tua controparte ha iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato presto alla tua disputa. Una volta assegnato, ti condividerò il loro npub e solo loro potranno assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa. L'ID di questa disputa è: {user_token}.", + "disputeInitiatedByYou": "Hai iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato presto alla tua disputa. Una volta assegnato, riceverai il suo npub e solo questo account potrà assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa. L'ID di questa disputa è: {dispute_id}.", + "disputeInitiatedByPeer": "La tua controparte ha iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato pronto alla tua disputa. Una volta assegnato, ti condividerò il loro npub e solo loro potranno assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa. L'ID di questa disputa è: {dispute_id}.", "adminTookDisputeAdmin": "Ecco i dettagli dell'ordine della disputa che hai preso: {details}. Devi determinare quale utente ha ragione e decidere se annullare o completare l'ordine. Nota che la tua decisione sarà finale e non può essere annullata.", - "adminTookDisputeUsers": "L'amministratore {admin_npub} gestirà la tua disputa. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa..", + "adminTookDisputeUsers": "Il risolutore {admin_npub} gestirà la tua controversia. Puoi contattarli direttamente.", "adminCanceledAdmin": "Hai annullato l'ordine ID: {id}!", "adminCanceledUsers": "L'amministratore ha annullato l'ordine ID: {id}!", "adminSettledAdmin": "Hai completato l'ordine ID: {id}!", diff --git a/lib/services/dispute_service.dart b/lib/services/dispute_service.dart index d7c5a34c..91a6ff38 100644 --- a/lib/services/dispute_service.dart +++ b/lib/services/dispute_service.dart @@ -1,3 +1,4 @@ +import 'dart:math'; import 'package:mostro_mobile/data/models/dispute.dart'; /// Stub service for disputes - UI only implementation @@ -33,9 +34,13 @@ class DisputeService { // Mock implementation await Future.delayed(const Duration(milliseconds: 300)); + // Safe prefix extraction to avoid RangeError + final prefixLen = min(8, disputeId.length); + final orderIdSuffix = disputeId.isEmpty ? 'unknown' : disputeId.substring(0, prefixLen); + return Dispute( disputeId: disputeId, - orderId: 'order_${disputeId.substring(0, 8)}', + orderId: 'order_$orderIdSuffix', status: 'initiated', createdAt: DateTime.now().subtract(const Duration(hours: 2)), action: 'dispute-initiated-by-you', From 710d92fe0f9cc2fbaba417bcf286f39f30448fa0 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Thu, 4 Sep 2025 19:47:36 -0300 Subject: [PATCH 6/9] chore: add placeholder metadata for dispute initiation messages in English translations --- lib/l10n/intl_en.arb | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 3500bb76..2cf8b179 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -106,8 +106,32 @@ "cooperativeCancelInitiatedByYou": "You have initiated the cancellation of the order ID: {id}. Your counterparty must agree. If they do not respond, you can open a dispute. Note that no administrator will contact you regarding this cancellation unless you open a dispute first.", "cooperativeCancelInitiatedByPeer": "Your counterparty wants to cancel order ID: {id}. If you agree, please send me cancel-order-message. Note that no administrator will contact you regarding this cancellation unless you open a dispute first.", "cooperativeCancelAccepted": "Order {id} has been successfully canceled!", - "disputeInitiatedByYou": "You have initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute ID is: {dispute_id}.", - "disputeInitiatedByPeer": "Your counterparty has initiated a dispute for order Id: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute ID is: {dispute_id}.", + "disputeInitiatedByYou": "You have initiated a dispute for order ID: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute ID is: {dispute_id}.", + "@disputeInitiatedByYou": { + "placeholders": { + "id": { + "type": "String", + "example": "abc123" + }, + "dispute_id": { + "type": "String", + "example": "dispute-456" + } + } + }, + "disputeInitiatedByPeer": "Your counterparty has initiated a dispute for order ID: {id}. A solver will be assigned soon. Once assigned, I will share their npub with you, and only they will be able to assist you. Your dispute ID is: {dispute_id}.", + "@disputeInitiatedByPeer": { + "placeholders": { + "id": { + "type": "String", + "example": "abc123" + }, + "dispute_id": { + "type": "String", + "example": "dispute-456" + } + } + }, "adminTookDisputeAdmin": "Here are the details of the dispute order you have taken: {details}. You need to determine which user is correct and decide whether to cancel or complete the order. Please note that your decision will be final and cannot be reversed.", "adminTookDisputeUsers": "The solver {admin_npub} will handle your dispute. You can contact them directly.", "adminCanceledAdmin": "You have canceled the order ID: {id}.", From d1ad4d0fe4c86f70f266d9ecc2753f6bd89ee995 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Fri, 5 Sep 2025 18:59:06 -0300 Subject: [PATCH 7/9] fix: update dispute protocol from NIP-17 to NIP-59 and remove admin npub from user notifications --- lib/data/repositories/dispute_repository.dart | 4 ++-- lib/l10n/intl_en.arb | 2 +- lib/l10n/intl_es.arb | 2 +- lib/l10n/intl_it.arb | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/data/repositories/dispute_repository.dart b/lib/data/repositories/dispute_repository.dart index 6013b34d..3e60ad60 100644 --- a/lib/data/repositories/dispute_repository.dart +++ b/lib/data/repositories/dispute_repository.dart @@ -33,13 +33,13 @@ class DisputeRepository { return false; } - // Create dispute message using Gift Wrap protocol (NIP-17) + // Create dispute message using Gift Wrap protocol (NIP-59) final disputeMessage = MostroMessage( action: Action.dispute, id: orderId, ); - // Wrap message using NIP-17 Gift Wrap protocol + // Wrap message using NIP-59 Gift Wrap protocol final event = await disputeMessage.wrap( tradeKey: session.tradeKey, recipientPubKey: _mostroPubkey, diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 2cf8b179..57dfc170 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -133,7 +133,7 @@ } }, "adminTookDisputeAdmin": "Here are the details of the dispute order you have taken: {details}. You need to determine which user is correct and decide whether to cancel or complete the order. Please note that your decision will be final and cannot be reversed.", - "adminTookDisputeUsers": "The solver {admin_npub} will handle your dispute. You can contact them directly.", + "adminTookDisputeUsers": "A dispute resolver has been assigned to handle your dispute. They will contact you through the app.", "adminCanceledAdmin": "You have canceled the order ID: {id}.", "adminCanceledUsers": "Admin has canceled the order ID: {id}.", "adminSettledAdmin": "You have completed the order ID: {id}.", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 1910cbce..a7063673 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -109,7 +109,7 @@ "disputeInitiatedByYou": "Has iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu ID de disputa es: {dispute_id}.", "disputeInitiatedByPeer": "Tu contraparte ha iniciado una disputa para la orden Id: {id}. Un resolutor será asignado pronto. Una vez asignado, compartiré su npub contigo, y solo ellos podrán ayudarte. Tu ID de disputa es: {dispute_id}.", "adminTookDisputeAdmin": "Aquí están los detalles de la orden en disputa que has tomado: {details}. Necesitas determinar qué usuario es correcto y decidir si cancelar o completar la orden. Ten en cuenta que tu decisión será final y no se puede revertir.", - "adminTookDisputeUsers": "El resolutor {admin_npub} manejará tu disputa. Puedes contactarlos directamente.", + "adminTookDisputeUsers": "Se ha asignado un resolutor de disputas para manejar tu caso. Te contactarán a través de la aplicación.", "adminCanceledAdmin": "Has cancelado la orden ID: {id}.", "adminCanceledUsers": "El administrador ha cancelado la orden ID: {id}.", "adminSettledAdmin": "Has completado la orden ID: {id}.", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 6a7a9325..495aa51c 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -109,7 +109,7 @@ "disputeInitiatedByYou": "Hai iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato presto alla tua disputa. Una volta assegnato, riceverai il suo npub e solo questo account potrà assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa. L'ID di questa disputa è: {dispute_id}.", "disputeInitiatedByPeer": "La tua controparte ha iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato pronto alla tua disputa. Una volta assegnato, ti condividerò il loro npub e solo loro potranno assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa. L'ID di questa disputa è: {dispute_id}.", "adminTookDisputeAdmin": "Ecco i dettagli dell'ordine della disputa che hai preso: {details}. Devi determinare quale utente ha ragione e decidere se annullare o completare l'ordine. Nota che la tua decisione sarà finale e non può essere annullata.", - "adminTookDisputeUsers": "Il risolutore {admin_npub} gestirà la tua controversia. Puoi contattarli direttamente.", + "adminTookDisputeUsers": "È stato assegnato un risolutore di controversie per gestire il tuo caso. Ti contatteranno attraverso l'applicazione.", "adminCanceledAdmin": "Hai annullato l'ordine ID: {id}!", "adminCanceledUsers": "L'amministratore ha annullato l'ordine ID: {id}!", "adminSettledAdmin": "Hai completato l'ordine ID: {id}!", From e8ce6a22b261ec449695babedcab527d0d176c07 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Fri, 5 Sep 2025 19:24:51 -0300 Subject: [PATCH 8/9] fix: remove hardcoded admin token from dispute message text --- lib/features/trades/widgets/mostro_message_detail_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/features/trades/widgets/mostro_message_detail_widget.dart b/lib/features/trades/widgets/mostro_message_detail_widget.dart index 099763bd..3c148ca0 100644 --- a/lib/features/trades/widgets/mostro_message_detail_widget.dart +++ b/lib/features/trades/widgets/mostro_message_detail_widget.dart @@ -213,7 +213,7 @@ class MostroMessageDetail extends ConsumerWidget { .of(context)! .disputeInitiatedByPeer(orderPayload!.id!, payload!.disputeId); case actions.Action.adminTookDispute: - return S.of(context)!.adminTookDisputeUsers('{admin token}'); + return S.of(context)!.adminTookDisputeUsers; case actions.Action.adminCanceled: return S.of(context)!.adminCanceledUsers(orderPayload!.id ?? ''); case actions.Action.adminSettled: From 673b78d03ac3355e7a135280bebbab245079d547 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Fri, 5 Sep 2025 19:30:01 -0300 Subject: [PATCH 9/9] feat: add trade key validation --- lib/data/repositories/dispute_repository.dart | 8 +++++- lib/l10n/intl_it.arb | 26 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/data/repositories/dispute_repository.dart b/lib/data/repositories/dispute_repository.dart index 3e60ad60..0ac53b7d 100644 --- a/lib/data/repositories/dispute_repository.dart +++ b/lib/data/repositories/dispute_repository.dart @@ -33,13 +33,19 @@ class DisputeRepository { return false; } + // Validate trade key is present + if (session.tradeKey.private.isEmpty) { + _logger.e('Trade key is empty for order: $orderId, cannot create dispute'); + return false; + } + // Create dispute message using Gift Wrap protocol (NIP-59) final disputeMessage = MostroMessage( action: Action.dispute, id: orderId, ); - // Wrap message using NIP-59 Gift Wrap protocol + // Wrap message using Gift Wrap protocol (NIP-59) final event = await disputeMessage.wrap( tradeKey: session.tradeKey, recipientPubKey: _mostroPubkey, diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 495aa51c..e9af4301 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -107,7 +107,31 @@ "cooperativeCancelInitiatedByPeer": "La tua controparte vuole annullare l'ordine ID: {id}. Nota che nessun amministratore ti contatterà MAI riguardo questo annullamento a meno che tu non apra prima una disputa. Se concordi su tale annullamento, premi: Annulla Ordine.", "cooperativeCancelAccepted": "L'ordine {id} è stato annullato con successo!", "disputeInitiatedByYou": "Hai iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato presto alla tua disputa. Una volta assegnato, riceverai il suo npub e solo questo account potrà assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa. L'ID di questa disputa è: {dispute_id}.", - "disputeInitiatedByPeer": "La tua controparte ha iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato pronto alla tua disputa. Una volta assegnato, ti condividerò il loro npub e solo loro potranno assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa. L'ID di questa disputa è: {dispute_id}.", + "disputeInitiatedByPeer": "La tua controparte ha iniziato una disputa per l'ordine ID: {id}. Un amministratore sarà assegnato presto alla tua disputa. Una volta assegnato, ti condividerò il loro npub e solo loro potranno assisterti. Devi contattare l'amministratore direttamente, ma se qualcuno ti contatta prima, assicurati di chiedergli di fornirti l'ID per la tua disputa. L'ID di questa disputa è: {dispute_id}.", + "@disputeInitiatedByPeer": { + "placeholders": { + "id": { + "type": "String", + "description": "ID dell'ordine" + }, + "dispute_id": { + "type": "String", + "description": "ID della disputa" + } + } + }, + "@disputeInitiatedByYou": { + "placeholders": { + "id": { + "type": "String", + "description": "ID dell'ordine" + }, + "dispute_id": { + "type": "String", + "description": "ID della disputa" + } + } + }, "adminTookDisputeAdmin": "Ecco i dettagli dell'ordine della disputa che hai preso: {details}. Devi determinare quale utente ha ragione e decidere se annullare o completare l'ordine. Nota che la tua decisione sarà finale e non può essere annullata.", "adminTookDisputeUsers": "È stato assegnato un risolutore di controversie per gestire il tuo caso. Ti contatteranno attraverso l'applicazione.", "adminCanceledAdmin": "Hai annullato l'ordine ID: {id}!",