Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
35 changes: 33 additions & 2 deletions assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -764,5 +764,36 @@
"fetchingPrivateKeysTitle": "Fetching Private Keys...",
"fetchingPrivateKeysMessage": "Please wait while we securely fetch your private keys...",
"pubkeyType": "Type",
"securitySettings": "Security Settings"
}
"securitySettings": "Security Settings",
"sendToAddress": "Only send {} to this address",
"message": "Message",
"signMessage": "Sign Message",
"selectedAddress": "Selected Address",
"selectAddress": "Select Address",
"messageToSign": "Message to Sign",
"enterMessage": "Enter message",
"signMessageButton": "Sign Message",
"signedMessage": "Signed Message",
"pleaseSelectAddress": "Please select an address first",
"pleaseEnterMessage": "Please enter a message to sign",
"failedToSignMessage": "Failed to sign message: {}",
"swapCoin": "Swap",
"komodoWalletSeed": "Komodo Wallet seed",
"failedToLoadAddresses": "Failed to load addresses: {}",
"sendFeedbackButton": "Share your feedback",
"allowCustomFee": "Allow custom seed",
"copyToClipboard": "Copy to clipboard",
"copyAllDetails": "Copy all details",
"zeroBalanceTooltip": "Insufficient balance to use Bitrefill",
"swapAddress": "Swap Address",
"userNotFoundError": "User not found",
"loginFailedError": "Login failed",
"previewWithdrawal": "Preview Withdrawal",
"chart": "Chart",
"confirmMessageSigning": "Confirm Message Signing",
"messageSigningWarning": "Only sign messages from trusted sources.",
"messageSigningCheckboxText": "I understand that signing proves ownership of this address.",
"messageSigned": "Message signed",
"addressLabel": "Address - ",
"signingAddress": "Signing address"
}
Comment on lines +767 to +799
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Verify translation text on line 784.

The key allowCustomFee has the value "Allow custom seed", which appears inconsistent. Based on the key name, this should likely read "Allow custom fee" instead of "Allow custom seed".

Apply this diff if the intention is to allow custom fees:

-  "allowCustomFee": "Allow custom seed",
+  "allowCustomFee": "Allow custom fee",

If the text is correct and refers to custom seeds, consider renaming the key to allowCustomSeed for clarity.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"securitySettings": "Security Settings",
"sendToAddress": "Only send {} to this address",
"message": "Message",
"signMessage": "Sign Message",
"selectedAddress": "Selected Address",
"selectAddress": "Select Address",
"messageToSign": "Message to Sign",
"enterMessage": "Enter message",
"signMessageButton": "Sign Message",
"signedMessage": "Signed Message",
"pleaseSelectAddress": "Please select an address first",
"pleaseEnterMessage": "Please enter a message to sign",
"failedToSignMessage": "Failed to sign message: {}",
"swapCoin": "Swap",
"komodoWalletSeed": "Komodo Wallet seed",
"failedToLoadAddresses": "Failed to load addresses: {}",
"sendFeedbackButton": "Share your feedback",
"allowCustomFee": "Allow custom seed",
"copyToClipboard": "Copy to clipboard",
"copyAllDetails": "Copy all details",
"zeroBalanceTooltip": "Insufficient balance to use Bitrefill",
"swapAddress": "Swap Address",
"userNotFoundError": "User not found",
"loginFailedError": "Login failed",
"previewWithdrawal": "Preview Withdrawal",
"chart": "Chart",
"confirmMessageSigning": "Confirm Message Signing",
"messageSigningWarning": "Only sign messages from trusted sources.",
"messageSigningCheckboxText": "I understand that signing proves ownership of this address.",
"messageSigned": "Message signed",
"addressLabel": "Address - ",
"signingAddress": "Signing address"
}
"allowCustomFee": "Allow custom fee",
🤖 Prompt for AI Agents
In assets/translations/en.json around lines 767 to 799 (line 784 specifically),
the value for key "allowCustomFee" is "Allow custom seed" which is inconsistent
with the key name; update the value to "Allow custom fee" to match the key, or
if the text truly refers to seeds instead of fees, rename the key to
"allowCustomSeed" and update all usages accordingly.

82 changes: 82 additions & 0 deletions docs/BLOC_NAMING_CONVENTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: Naming Conventions
description: Overview of the recommended naming conventions when using bloc.
---

import EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro';
import EventExamplesBad1 from '~/components/naming-conventions/EventExamplesBad1Snippet.astro';
import StateExamplesGood1Snippet from '~/components/naming-conventions/StateExamplesGood1Snippet.astro';
import SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro';
import StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro';

The following naming conventions are simply recommendations and are completely optional. Feel free to use whatever naming conventions you prefer. You may find some of the examples/documentation do not follow the naming conventions mainly for simplicity/conciseness. These conventions are strongly recommended for large projects with multiple developers.

## Event Conventions

Events should be named in the **past tense** because events are things that have already occurred from the bloc's perspective.

### Anatomy

`BlocSubject` + `Noun (optional)` + `Verb (event)`

Initial load events should follow the convention: `BlocSubject` + `Started`

:::note
The base event class should be name: `BlocSubject` + `Event`.
:::

### Examples

**Good**

<EventExamplesGood1 />

**Bad**

<EventExamplesBad1 />

## State Conventions

States should be nouns because a state is just a snapshot at a particular point in time. There are two common ways to represent state: using subclasses or using a single class.

### Anatomy

#### Subclasses

`BlocSubject` + `Verb (action)` + `State`

When representing the state as multiple subclasses `State` should be one of the following:

`Initial` | `Success` | `Failure` | `InProgress`

:::note
Initial states should follow the convention: `BlocSubject` + `Initial`.
:::

#### Single Class

`BlocSubject` + `State`

When representing the state as a single base class an enum named `BlocSubject` + `Status` should be used to represent the status of the state:

`initial` | `success` | `failure` | `loading`.

:::note
The base state class should always be named: `BlocSubject` + `State`.
:::

### Examples

**Good**

##### Subclasses
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix heading level jump.

The heading on line 72 jumps from h3 to h5, skipping h4. This violates markdown best practices and can cause issues with document structure and accessibility.

Apply this diff to correct the heading level:

-##### Subclasses
+#### Subclasses

And also fix line 76:

-##### Single Class
+#### Single Class
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
##### Subclasses
++ b/docs/BLOC_NAMING_CONVENTIONS.md
@@ -72,7 +72,7 @@
#### Subclasses
@@ -76,7 +76,7 @@
#### Single Class
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

72-72: Heading levels should only increment by one level at a time
Expected: h4; Actual: h5

(MD001, heading-increment)

🤖 Prompt for AI Agents
In docs/BLOC_NAMING_CONVENTIONS.md around lines 72 and 76, the heading level
jumps incorrectly (h3 -> h5); change the "##### Subclasses" heading on line 72
to "#### Subclasses" to restore the proper h4 level under the preceding h3, and
adjust the heading on line 76 to the correct level as well so the document
heading hierarchy remains sequential (no skipped levels).


<StateExamplesGood1Snippet />

##### Single Class

<SingleStateExamplesGood1Snippet />

**Bad**

<StateExamplesBad1Snippet />
101 changes: 101 additions & 0 deletions lib/bloc/message_signing/message_signing_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:komodo_defi_sdk/komodo_defi_sdk.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:web_dex/generated/codegen_loader.g.dart';
import 'package:web_dex/bloc/message_signing/message_signing_event.dart';
import 'package:web_dex/bloc/message_signing/message_signing_state.dart';

class MessageSigningBloc
extends Bloc<MessageSigningEvent, MessageSigningState> {
final KomodoDefiSdk sdk;

MessageSigningBloc(this.sdk) : super(MessageSigningState.initial()) {
on<MessageSigningAddressesRequested>(_onLoadAddresses);
on<MessageSigningAddressSelected>(_onSelectAddress);
on<MessageSigningFormSubmitted>(_onSubmitMessage);
on<MessageSigningInputConfirmed>(_onRequestConfirmation);
on<MessageSigningConfirmationCancelled>(_onCancelConfirmation);
}

Future<void> _onLoadAddresses(
MessageSigningAddressesRequested event,
Emitter<MessageSigningState> emit,
) async {
emit(state.copyWith(
status: MessageSigningStatus.loading, errorMessage: null));

try {
final result = await sdk.pubkeys.getPubkeys(event.asset);
final keys = result.keys;

emit(state.copyWith(
addresses: keys,
selected: keys.isNotEmpty ? keys.first : null,
status: MessageSigningStatus.ready,
));
} catch (e) {
emit(state.copyWith(
status: MessageSigningStatus.failure,
errorMessage: e.toString(),
));
}
}

void _onSelectAddress(
MessageSigningAddressSelected event,
Emitter<MessageSigningState> emit,
) {
emit(state.copyWith(selected: event.address));
}

void _onRequestConfirmation(
MessageSigningInputConfirmed event,
Emitter<MessageSigningState> emit,
) {
emit(state.copyWith(status: MessageSigningStatus.confirming));
}

void _onCancelConfirmation(
MessageSigningConfirmationCancelled event,
Emitter<MessageSigningState> emit,
) {
emit(state.copyWith(status: MessageSigningStatus.ready));
}

Future<void> _onSubmitMessage(
MessageSigningFormSubmitted event,
Emitter<MessageSigningState> emit,
) async {
final address = state.selected;
if (address == null) {
emit(state.copyWith(
errorMessage: LocaleKeys.pleaseSelectAddress.tr(),
status: MessageSigningStatus.failure,
));
return;
}

emit(state.copyWith(
status: MessageSigningStatus.submitting,
errorMessage: null,
));

try {
final signed = await sdk.messageSigning.signMessage(
coin: event.coinAbbr,
address: address.address,
message: event.message,
);

emit(state.copyWith(
signedMessage: signed,
status: MessageSigningStatus.success,
));
} catch (e) {
emit(state.copyWith(
errorMessage: LocaleKeys.failedToSignMessage.tr(args: [e.toString()]),
status: MessageSigningStatus.failure,
));
}
}
}
28 changes: 28 additions & 0 deletions lib/bloc/message_signing/message_signing_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:komodo_defi_types/komodo_defi_types.dart';

sealed class MessageSigningEvent {}

class MessageSigningAddressesRequested extends MessageSigningEvent {
final Asset asset;

MessageSigningAddressesRequested(this.asset);
}

class MessageSigningAddressSelected extends MessageSigningEvent {
final PubkeyInfo address;

MessageSigningAddressSelected(this.address);
}

class MessageSigningFormSubmitted extends MessageSigningEvent {
final String message;
final String coinAbbr;

MessageSigningFormSubmitted({
required this.message,
required this.coinAbbr,
});
}

class MessageSigningInputConfirmed extends MessageSigningEvent {}
class MessageSigningConfirmationCancelled extends MessageSigningEvent {}
61 changes: 61 additions & 0 deletions lib/bloc/message_signing/message_signing_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:equatable/equatable.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';

enum MessageSigningStatus {
initial,
loading,
ready,
confirming,
submitting,
success,
failure,
}

class MessageSigningState extends Equatable {
final List<PubkeyInfo> addresses;
final PubkeyInfo? selected;
final String? signedMessage;
final String? errorMessage;
final MessageSigningStatus status;

const MessageSigningState({
required this.addresses,
required this.selected,
required this.signedMessage,
required this.errorMessage,
required this.status,
});

factory MessageSigningState.initial() => const MessageSigningState(
addresses: [],
selected: null,
signedMessage: null,
errorMessage: null,
status: MessageSigningStatus.initial,
);

MessageSigningState copyWith({
List<PubkeyInfo>? addresses,
PubkeyInfo? selected,
String? signedMessage,
String? errorMessage,
MessageSigningStatus? status,
}) {
return MessageSigningState(
addresses: addresses ?? this.addresses,
selected: selected ?? this.selected,
signedMessage: signedMessage ?? this.signedMessage,
errorMessage: errorMessage ?? this.errorMessage,
status: status ?? this.status,
);
}
Comment on lines +37 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify if nullable fields need explicit null-setting.

The copyWith implementation uses ?? fallback, which prevents explicitly setting nullable fields (selected, signedMessage, errorMessage) to null. For example, copyWith(selected: null) will retain the current selected value instead of clearing it.

If your Bloc logic requires explicitly nulling these fields during state transitions, consider using an Optional<T> wrapper or a sentinel pattern to distinguish "not provided" from "explicitly null".

Run the following script to check if any Bloc events or handlers attempt to explicitly null these fields:


🏁 Script executed:

#!/bin/bash
# Description: Search for copyWith calls in MessageSigningBloc that may attempt to explicitly set nullable fields to null.

# Search for copyWith calls in the message_signing bloc and related files
rg -n -A 2 -B 2 --type=dart 'copyWith\s*\(' lib/bloc/message_signing/

Length of output: 4449


copyWith doesn’t support explicit null resets

In lib/bloc/message_signing/message_signing_state.dart (copyWith at lines 37–51), all nullable parameters fall back via param ?? this.field. As a result, calls like

  • emit(state.copyWith(status: MessageSigningStatus.loading, errorMessage: null)); (message_signing_bloc.dart:24–26)
  • emit(state.copyWith(selected: null)); (message_signing_bloc.dart:48–49)
  • emit(state.copyWith(status: MessageSigningStatus.submitting, errorMessage: null)); (message_signing_bloc.dart:78–80)

will not clear those fields but instead retain their previous values. Refactor copyWith to distinguish “no override” from “override to null” (for example, by adding boolean flags per field, using an Optional<T> wrapper, or switching to a freezed-generated copyWith) so nullable fields can be explicitly set to null.


@override
List<Object?> get props => [
addresses,
selected,
signedMessage,
errorMessage,
status,
];
}
Loading
Loading