Skip to content
Merged
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
2 changes: 2 additions & 0 deletions assets/languages/strings_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
"registerPhoneNumberInvalid": "Telefonnummer ist erforderlich",
"registerPhoneNumberOnlyDigits": "Nur Zahlen sind erlaubt",
"registerPhoneNumberTooShort": "Telefonnummer ist zu kurz",
"registrationFailed": "Registrierung fehlgeschlagen:\n${message}",
"registrationRequired": "Registrierung erforderlich",
"registrationRequiredDescription": "Um RealUnit Token kaufen zu können, müssen Sie sich einmalig registrieren.",
"reset": "Zurücksetzen",
Expand Down Expand Up @@ -254,6 +255,7 @@
"settingsWalletBackupSubtitle2": "Dies ist die einzige Möglichkeit, Ihre Wallet wiederherzustellen.",
"showSeed": "Seed anzeigen",
"signature": "Signatur",
"signingCancelled": "Signatur abgebrochen — bitte BitBox erneut bestätigen",
"signMessage": "Signierte Nachricht",
"signMessageGet": "Signierte Nachricht abrufen",
"skip": "Überspringen",
Expand Down
2 changes: 2 additions & 0 deletions assets/languages/strings_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
"registerPhoneNumberInvalid": "Phone number is required",
"registerPhoneNumberOnlyDigits": "Only numbers are allowed",
"registerPhoneNumberTooShort": "Phone number is too short",
"registrationFailed": "Registration failed:\n${message}",
"registrationRequired": "Registration required",
"registrationRequiredDescription": "To purchase RealUnit tokens, you must register once.",
"reset": "Reset",
Expand Down Expand Up @@ -254,6 +255,7 @@
"settingsWalletBackupSubtitle2": "This is the only way to recover your wallet.",
"showSeed": "Show Seed",
"signature": "Signature",
"signingCancelled": "Signature cancelled — please confirm on the BitBox again",
"signMessage": "Sign Message",
"signMessageGet": "Get Sign Message",
"skip": "Skip",
Expand Down
3 changes: 2 additions & 1 deletion lib/packages/service/dfx/dfx_auth_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:convert';

import 'package:realunit_wallet/packages/config/api_config.dart';
import 'package:realunit_wallet/packages/service/app_store.dart';
import 'package:realunit_wallet/packages/wallet/exceptions/signing_cancelled_exception.dart';
import 'package:realunit_wallet/packages/wallet/wallet_account.dart';

abstract class DFXAuthService {
Expand Down Expand Up @@ -48,7 +49,7 @@ abstract class DFXAuthService {

final signature = await wallet.signMessage(message).timeout(_signMessageTimeout);
if (signature.isEmpty || signature == '0x') {
throw Exception('Wallet returned an empty signature');
throw const SigningCancelledException();
}
await appStore.sessionCache.saveSignature(walletAddress, signature);

Expand Down
3 changes: 2 additions & 1 deletion lib/packages/wallet/eip712_signer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:eth_sig_util_plus/eth_sig_util_plus.dart';
import 'package:realunit_wallet/packages/hardware_wallet/bitbox_credentials.dart';
import 'package:realunit_wallet/packages/service/dfx/models/payment/sell/dto/eip7702/eip7702_data_dto.dart';
import 'package:realunit_wallet/packages/wallet/exceptions/signing_cancelled_exception.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';

Expand Down Expand Up @@ -126,7 +127,7 @@ class Eip712Signer {
// guard the empty sig would be sent to the backend and the abort would
// be misread as a successful sign.
if (signature.isEmpty || signature == '0x') {
throw Exception('Signature was empty — wallet may have been cancelled or disconnected');
throw const SigningCancelledException();
}
return signature;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class SigningCancelledException implements Exception {
const SigningCancelledException();

@override
String toString() => 'SigningCancelledException';
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class KycRegistrationSubmitCubit extends Cubit<KycRegistrationSubmitState> {
}
} catch (e) {
developer.log(e.toString());
emit(KycRegistrationSubmitFailure(e.toString()));
emit(KycRegistrationSubmitFailure(e.toString(), cause: e));
return;
}
}
Expand All @@ -92,7 +92,7 @@ class KycRegistrationSubmitCubit extends Cubit<KycRegistrationSubmitState> {
emit(const KycRegistrationSubmitSuccess(RegistrationStatus.completed));
} catch (e) {
developer.log(e.toString());
emit(KycRegistrationSubmitFailure(e.toString()));
emit(KycRegistrationSubmitFailure(e.toString(), cause: e));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ class KycRegistrationSubmitSuccess extends KycRegistrationSubmitState {

class KycRegistrationSubmitFailure extends KycRegistrationSubmitState {
final String message;
final Object? cause;

const KycRegistrationSubmitFailure(this.message);
const KycRegistrationSubmitFailure(this.message, {this.cause});

@override
List<Object?> get props => [message];
List<Object?> get props => [message, cause];
}

class KycRegistrationSubmitBitboxRequired extends KycRegistrationSubmitState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:realunit_wallet/generated/i18n.dart';
import 'package:realunit_wallet/packages/service/dfx/dfx_kyc_service.dart';
import 'package:realunit_wallet/packages/service/dfx/models/country/country.dart';
import 'package:realunit_wallet/packages/service/dfx/models/registration/registration_status.dart';
import 'package:realunit_wallet/packages/service/dfx/models/registration/registration_user_type.dart';
import 'package:realunit_wallet/packages/service/dfx/real_unit_registration_service.dart';
import 'package:realunit_wallet/packages/wallet/exceptions/signing_cancelled_exception.dart';
import 'package:realunit_wallet/screens/hardware_connect_bitbox/connect_bitbox_page.dart';
import 'package:realunit_wallet/screens/home/bloc/home_bloc.dart';
import 'package:realunit_wallet/screens/kyc/cubits/kyc/kyc_cubit.dart';
Expand Down Expand Up @@ -109,9 +111,12 @@ class _KycRegistrationViewState extends State<KycRegistrationView> {
}
}
if (state is KycRegistrationSubmitFailure) {
final message = state.cause is SigningCancelledException
? S.of(context).signingCancelled
: S.of(context).registrationFailed(state.message);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Registration failed:\n${state.message}'),
content: Text(message),
backgroundColor: RealUnitColors.status.red600,
),
);
Expand Down
36 changes: 36 additions & 0 deletions test/packages/wallet/eip712_signer_test.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:realunit_wallet/packages/hardware_wallet/bitbox_credentials.dart';
import 'package:realunit_wallet/packages/service/dfx/models/country/country.dart';
import 'package:realunit_wallet/packages/service/dfx/models/registration/registration.dart';
import 'package:realunit_wallet/packages/service/dfx/models/registration/registration_user_type.dart';
import 'package:realunit_wallet/packages/wallet/eip712_signer.dart';
import 'package:realunit_wallet/packages/wallet/exceptions/signing_cancelled_exception.dart';
import 'package:web3dart/web3dart.dart';

class _MockBitboxCredentials extends Mock implements BitboxCredentials {}

Future<String> _signWith(BitboxCredentials credentials) => Eip712Signer.signRegistration(
credentials: credentials,
chainId: 1,
type: RegistrationUserType.human.jsonName,
email: 'cancel@dfx.swiss',
name: 'Cancel User',
phoneNumber: '+41790000000',
birthday: '1990-01-01',
nationality: 'CH',
addressStreet: 'Teststrasse 1',
addressPostalCode: '8000',
addressCity: 'Zurich',
addressCountry: 'CH',
swissTaxResidence: true,
registrationDate: '2026-05-12',
);

void main() {
late String privateKeyHex;
late RegistrationUserType type;
Expand Down Expand Up @@ -79,5 +101,19 @@ void main() {
'0xa11cb57186b9c9f9a09fafa7a3aa256ab14ca030d7eba89f35026b64925d617b3e2cb15349ca561fae5e431deed3f1aa69c7d391cfba80aa6111e753fa782ea21c',
);
});

for (final emptySignature in const ['', '0x']) {
test('throws SigningCancelledException when BitBox returns "$emptySignature"', () async {
final credentials = _MockBitboxCredentials();
when(
() => credentials.address,
).thenReturn(EthereumAddress.fromHex('0x0000000000000000000000000000000000000001'));
when(
() => credentials.signTypedDataV4(any(), any()),
).thenAnswer((_) async => emptySignature);

expect(() => _signWith(credentials), throwsA(isA<SigningCancelledException>()));
});
}
});
}
Loading