diff --git a/mobile-app/lib/providers/multisig_approval_toast_provider.dart b/mobile-app/lib/providers/multisig_approval_toast_provider.dart index d2ac92ee..47387997 100644 --- a/mobile-app/lib/providers/multisig_approval_toast_provider.dart +++ b/mobile-app/lib/providers/multisig_approval_toast_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -enum MultisigApprovalToastKind { timeout, submitFailed } +enum MultisigApprovalToastKind { timeout } class MultisigApprovalToastEvent { const MultisigApprovalToastEvent(this.kind); diff --git a/mobile-app/lib/providers/multisig_cancellation_toast_provider.dart b/mobile-app/lib/providers/multisig_cancellation_toast_provider.dart index d3eae6eb..30136363 100644 --- a/mobile-app/lib/providers/multisig_cancellation_toast_provider.dart +++ b/mobile-app/lib/providers/multisig_cancellation_toast_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -enum MultisigCancellationToastKind { timeout, submitFailed } +enum MultisigCancellationToastKind { timeout } class MultisigCancellationToastEvent { const MultisigCancellationToastEvent(this.kind); diff --git a/mobile-app/lib/providers/multisig_creation_toast_provider.dart b/mobile-app/lib/providers/multisig_creation_toast_provider.dart index 2a42b1a6..4b83bcf9 100644 --- a/mobile-app/lib/providers/multisig_creation_toast_provider.dart +++ b/mobile-app/lib/providers/multisig_creation_toast_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/legacy.dart'; -enum MultisigCreationToastKind { ready, timeout, submitFailed } +enum MultisigCreationToastKind { ready, timeout } class MultisigCreationToastEvent { const MultisigCreationToastEvent(this.kind); diff --git a/mobile-app/lib/providers/multisig_execution_toast_provider.dart b/mobile-app/lib/providers/multisig_execution_toast_provider.dart index 9a3b565d..151e1912 100644 --- a/mobile-app/lib/providers/multisig_execution_toast_provider.dart +++ b/mobile-app/lib/providers/multisig_execution_toast_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -enum MultisigExecutionToastKind { timeout, submitFailed, executedByOther } +enum MultisigExecutionToastKind { timeout, executedByOther } class MultisigExecutionToastEvent { const MultisigExecutionToastEvent(this.kind); diff --git a/mobile-app/lib/providers/multisig_proposal_toast_provider.dart b/mobile-app/lib/providers/multisig_proposal_toast_provider.dart index 6946d37b..d1ab936e 100644 --- a/mobile-app/lib/providers/multisig_proposal_toast_provider.dart +++ b/mobile-app/lib/providers/multisig_proposal_toast_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -enum MultisigProposalToastKind { timeout, submitFailed } +enum MultisigProposalToastKind { timeout } class MultisigProposalToastEvent { const MultisigProposalToastEvent(this.kind); diff --git a/mobile-app/lib/services/multisig_submission_service.dart b/mobile-app/lib/services/multisig_submission_service.dart index 962d6f87..2f59b317 100644 --- a/mobile-app/lib/services/multisig_submission_service.dart +++ b/mobile-app/lib/services/multisig_submission_service.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:convert/convert.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/providers/multisig_creation_toast_provider.dart'; import 'package:resonance_network_wallet/providers/multisig_providers.dart'; import 'package:resonance_network_wallet/providers/pending_multisig_creations_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; @@ -30,12 +29,14 @@ class MultisigSubmissionService { await _runCreationPreflight(name: '', signers: signers, threshold: threshold, creator: creator, nonce: nonce); } - /// Preflight on-chain state, then submit and track creation in the background. + /// Preflight on-chain state, then submit and track creation. /// - /// Returns when preflight passes and background work is scheduled. Throws + /// Awaits acceptance of the creation extrinsic by the chain before + /// completing; indexer polling then continues in the background. Throws /// [MultisigAlreadyExistsException] if the predicted address already exists, /// or [MultisigInsufficientBalanceException] if the creator cannot afford - /// the total creation cost. + /// the total creation cost. Rethrows on submission failure so callers can + /// surface the error instead of optimistically navigating away. Future startMultisigCreation({ required String name, required List signers, @@ -59,18 +60,10 @@ class MultisigSubmissionService { .read(pendingMultisigCreationsProvider.notifier) .add(PendingMultisigCreationEvent.fromDraft(draft, networkFee: networkFee), draft); - unawaited( - _submitAndTrackBackground( - creator: creator, - signers: signers, - threshold: threshold, - nonce: draft.nonce, - draft: draft, - ), - ); + await _submitAndTrack(creator: creator, signers: signers, threshold: threshold, nonce: draft.nonce, draft: draft); } - Future _submitAndTrackBackground({ + Future _submitAndTrack({ required Account creator, required List signers, required int threshold, @@ -104,9 +97,7 @@ class MultisigSubmissionService { quantusDebugPrint('Stack trace: $stackTrace'); TelemetryService().sendError('multisig_create_submit_failed', error: e, stackTrace: stackTrace); removePendingMultisigCreation(_ref, draft.accountId); - _ref.read(multisigCreationToastProvider.notifier).state = const MultisigCreationToastEvent( - MultisigCreationToastKind.submitFailed, - ); + rethrow; } } diff --git a/mobile-app/lib/services/transaction_submission_service.dart b/mobile-app/lib/services/transaction_submission_service.dart index a442e876..5285787d 100644 --- a/mobile-app/lib/services/transaction_submission_service.dart +++ b/mobile-app/lib/services/transaction_submission_service.dart @@ -1,6 +1,5 @@ // mobile-app/lib/services/transaction_submission_service.dart -import 'dart:async'; import 'dart:typed_data'; import 'package:convert/convert.dart'; @@ -8,10 +7,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/notification_provider.dart'; -import 'package:resonance_network_wallet/providers/multisig_approval_toast_provider.dart'; -import 'package:resonance_network_wallet/providers/multisig_cancellation_toast_provider.dart'; -import 'package:resonance_network_wallet/providers/multisig_execution_toast_provider.dart'; -import 'package:resonance_network_wallet/providers/multisig_proposal_toast_provider.dart'; import 'package:resonance_network_wallet/providers/multisig_providers.dart'; import 'package:resonance_network_wallet/providers/pending_multisig_approvals_provider.dart'; import 'package:resonance_network_wallet/providers/pending_multisig_cancellations_provider.dart'; @@ -129,8 +124,12 @@ class TransactionSubmissionService { ); } - /// Submits a multisig transfer proposal, tracks it optimistically, and polls - /// the indexer until the proposal is visible. + /// Submits a multisig transfer proposal and tracks it optimistically. + /// + /// Awaits acceptance of the extrinsic by the chain before completing; + /// indexer polling then continues in the background. Rethrows on submission + /// failure so callers can surface the error instead of optimistically + /// navigating away. Future proposeTransfer({ required MultisigAccount msig, required Account signer, @@ -154,20 +153,21 @@ class TransactionSubmissionService { TelemetryService().sendEvent('multisig_propose'); - unawaited( - _submitProposalBackground( - msig: msig, - signer: signer, - recipient: recipient, - amount: amount, - expiryBlock: expiryBlock, - pending: pending, - ), + await _submitProposal( + msig: msig, + signer: signer, + recipient: recipient, + amount: amount, + expiryBlock: expiryBlock, + pending: pending, ); } - /// Submits a multisig proposal approval, tracks it optimistically, and polls - /// the indexer until the approval appears on the proposal. + /// Submits a multisig proposal approval and tracks it optimistically. + /// + /// Awaits acceptance of the extrinsic by the chain before completing; + /// indexer polling then continues in the background. Rethrows on submission + /// failure so callers can surface the error. Future approveProposal({ required MultisigAccount msig, required Account signer, @@ -183,10 +183,10 @@ class TransactionSubmissionService { TelemetryService().sendEvent('multisig_approve'); - unawaited(_submitApproveBackground(msig: msig, signer: signer, proposalId: proposal.id, pending: pending)); + await _submitApprove(msig: msig, signer: signer, proposalId: proposal.id, pending: pending); } - Future _submitApproveBackground({ + Future _submitApprove({ required MultisigAccount msig, required Account signer, required int proposalId, @@ -204,12 +204,15 @@ class TransactionSubmissionService { } catch (e, stackTrace) { quantusDebugPrint('[Approve] submit failed: $e\n$stackTrace'); removePendingMultisigApproval(_ref, pending.id); - _ref.read(multisigApprovalToastProvider.notifier).show(MultisigApprovalToastKind.submitFailed); + rethrow; } } - /// Submits a multisig proposal execution, tracks it optimistically, and polls - /// the indexer until the proposal status becomes executed. + /// Submits a multisig proposal execution and tracks it optimistically. + /// + /// Awaits acceptance of the extrinsic by the chain before completing; + /// indexer polling then continues in the background. Rethrows on submission + /// failure so callers can surface the error. Future executeProposal({ required MultisigAccount msig, required Account signer, @@ -227,10 +230,10 @@ class TransactionSubmissionService { TelemetryService().sendEvent('multisig_execute'); - unawaited(_submitExecuteBackground(msig: msig, signer: signer, proposalId: proposal.id, pending: pending)); + await _submitExecute(msig: msig, signer: signer, proposalId: proposal.id, pending: pending); } - Future _submitExecuteBackground({ + Future _submitExecute({ required MultisigAccount msig, required Account signer, required int proposalId, @@ -248,12 +251,15 @@ class TransactionSubmissionService { } catch (e, stackTrace) { quantusDebugPrint('[Execute] submit failed: $e\n$stackTrace'); removePendingMultisigExecution(_ref, pending.id); - _ref.read(multisigExecutionToastProvider.notifier).show(MultisigExecutionToastKind.submitFailed); + rethrow; } } - /// Submits a multisig proposal cancellation, tracks it optimistically, and polls - /// the indexer until the proposal status becomes cancelled. + /// Submits a multisig proposal cancellation and tracks it optimistically. + /// + /// Awaits acceptance of the extrinsic by the chain before completing; + /// indexer polling then continues in the background. Rethrows on submission + /// failure so callers can surface the error. Future cancelProposal({ required MultisigAccount msig, required Account proposer, @@ -271,10 +277,10 @@ class TransactionSubmissionService { TelemetryService().sendEvent('multisig_cancel'); - unawaited(_submitCancelBackground(msig: msig, proposer: proposer, proposalId: proposal.id, pending: pending)); + await _submitCancel(msig: msig, proposer: proposer, proposalId: proposal.id, pending: pending); } - Future _submitCancelBackground({ + Future _submitCancel({ required MultisigAccount msig, required Account proposer, required int proposalId, @@ -293,11 +299,11 @@ class TransactionSubmissionService { } catch (e, stackTrace) { quantusDebugPrint('[Cancel] submit failed: $e\n$stackTrace'); removePendingMultisigCancellation(_ref, pending.id); - _ref.read(multisigCancellationToastProvider.notifier).show(MultisigCancellationToastKind.submitFailed); + rethrow; } } - Future _submitProposalBackground({ + Future _submitProposal({ required MultisigAccount msig, required Account signer, required String recipient, @@ -326,7 +332,7 @@ class TransactionSubmissionService { // deposit-reserving proposals if a prior submit already landed. quantusDebugPrint('[Propose] submit failed: $e\n$stackTrace'); removePendingMultisigProposal(_ref, pending.id); - _ref.read(multisigProposalToastProvider.notifier).show(MultisigProposalToastKind.submitFailed); + rethrow; } } diff --git a/mobile-app/lib/v2/components/multisig_approval_toast_listener.dart b/mobile-app/lib/v2/components/multisig_approval_toast_listener.dart index f2c91420..00a134e0 100644 --- a/mobile-app/lib/v2/components/multisig_approval_toast_listener.dart +++ b/mobile-app/lib/v2/components/multisig_approval_toast_listener.dart @@ -19,7 +19,6 @@ class MultisigApprovalToastListener extends ConsumerWidget { final l10n = ref.read(l10nProvider); final message = switch (next.kind) { MultisigApprovalToastKind.timeout => l10n.multisigApprovalTimeoutToast, - MultisigApprovalToastKind.submitFailed => l10n.multisigApproveFailed, }; context.showErrorToaster(message: message); ref.read(multisigApprovalToastProvider.notifier).clear(); @@ -31,8 +30,6 @@ class MultisigApprovalToastListener extends ConsumerWidget { switch (next.kind) { case MultisigExecutionToastKind.timeout: context.showErrorToaster(message: l10n.multisigExecutionTimeoutToast); - case MultisigExecutionToastKind.submitFailed: - context.showErrorToaster(message: l10n.multisigExecuteFailed); case MultisigExecutionToastKind.executedByOther: context.showInfoToaster(message: l10n.multisigExecutedByOtherToast); } @@ -44,7 +41,6 @@ class MultisigApprovalToastListener extends ConsumerWidget { final l10n = ref.read(l10nProvider); final message = switch (next.kind) { MultisigCancellationToastKind.timeout => l10n.multisigCancelTimeoutToast, - MultisigCancellationToastKind.submitFailed => l10n.multisigCancelFailed, }; context.showErrorToaster(message: message); ref.read(multisigCancellationToastProvider.notifier).clear(); diff --git a/mobile-app/lib/v2/components/multisig_creation_toast_listener.dart b/mobile-app/lib/v2/components/multisig_creation_toast_listener.dart index 39ad4de6..fa609563 100644 --- a/mobile-app/lib/v2/components/multisig_creation_toast_listener.dart +++ b/mobile-app/lib/v2/components/multisig_creation_toast_listener.dart @@ -18,7 +18,6 @@ class MultisigCreationToastListener extends ConsumerWidget { final message = switch (next.kind) { MultisigCreationToastKind.ready => l10n.multisigCreateReadyToast, MultisigCreationToastKind.timeout => l10n.multisigCreateTimeoutToast, - MultisigCreationToastKind.submitFailed => l10n.multisigCreateErrorCouldNotCreate, }; if (next.kind == MultisigCreationToastKind.ready) { context.showSuccessToaster(message: message); diff --git a/mobile-app/lib/v2/components/multisig_proposal_toast_listener.dart b/mobile-app/lib/v2/components/multisig_proposal_toast_listener.dart index 9933f09a..3b06b416 100644 --- a/mobile-app/lib/v2/components/multisig_proposal_toast_listener.dart +++ b/mobile-app/lib/v2/components/multisig_proposal_toast_listener.dart @@ -17,7 +17,6 @@ class MultisigProposalToastListener extends ConsumerWidget { final l10n = ref.read(l10nProvider); final message = switch (next.kind) { MultisigProposalToastKind.timeout => l10n.multisigProposeTimeoutToast, - MultisigProposalToastKind.submitFailed => l10n.multisigProposeSubmitFailed, }; context.showErrorToaster(message: message); ref.read(multisigProposalToastProvider.notifier).clear(); diff --git a/mobile-app/lib/v2/screens/multisig/add_multisig_screen.dart b/mobile-app/lib/v2/screens/multisig/add_multisig_screen.dart index ca83e960..2bc1f5d9 100644 --- a/mobile-app/lib/v2/screens/multisig/add_multisig_screen.dart +++ b/mobile-app/lib/v2/screens/multisig/add_multisig_screen.dart @@ -269,8 +269,10 @@ class _AddMultisigScreenState extends ConsumerState { context.showErrorToaster(message: l10n.multisigCreateInsufficientBalance); } } catch (e) { + // The submission service rethrows without surfacing UI; the caller owns + // user feedback. Show a single error toast for any failure (submission or + // otherwise) and skip navigation. quantusDebugPrint('[AddMultisigScreen] createMultisig error: $e'); - if (mounted) { context.showErrorToaster(message: l10n.multisigCreateErrorCouldNotCreate); }