diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index 3fce297..d7fd112 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -30,6 +30,13 @@ + + + + + + @@ -44,6 +51,13 @@ + + + + + + @@ -86,6 +100,13 @@ + + + + + + @@ -93,6 +114,20 @@ + + + + + + + + + + + + @@ -142,6 +177,13 @@ + + + + + + @@ -156,6 +198,20 @@ + + + + + + + + + + + + @@ -184,6 +240,13 @@ + + + + + + @@ -254,6 +317,20 @@ + + + + + + + + + + + + @@ -289,6 +366,13 @@ + + + + + + @@ -324,6 +408,13 @@ + + + + + + @@ -450,26 +541,35 @@ + + + + + + + + + @@ -480,15 +580,19 @@ + + + + diff --git a/lib/data/base/text_value.dart b/lib/data/base/text_value.dart index f725eb2..019ae13 100644 --- a/lib/data/base/text_value.dart +++ b/lib/data/base/text_value.dart @@ -2,7 +2,7 @@ import '../error/input_error.dart'; class TextValue { TextValue({ - required this.text, + this.text = "", this.error, }); diff --git a/lib/data/error/input_error.dart b/lib/data/error/input_error.dart index d038c98..b552079 100644 --- a/lib/data/error/input_error.dart +++ b/lib/data/error/input_error.dart @@ -16,3 +16,13 @@ class RequiredAmount extends InputError { @override String text = "Field is required"; } + +class KeyNotBase58Encoded extends InputError { + @override + String text = "Invalid value, must be base58 encoded"; +} + +class KeyLengthInvalid extends InputError { + @override + String text = "Key value must be between 32 and 44 characters"; +} diff --git a/lib/di/blocs_providers.dart b/lib/di/blocs_providers.dart index be279c6..bc486b8 100644 --- a/lib/di/blocs_providers.dart +++ b/lib/di/blocs_providers.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sol_pay_gen/domain/generate_transfer_request_qr_use_case.dart'; import 'package:sol_pay_gen/feature/qr/bloc/qr_generator_cubit.dart'; +import 'package:sol_pay_gen/validator/keys_validator.dart'; import 'package:sol_pay_gen/validator/number_validator.dart'; import '../data/transfer/transfer_request_repository.dart'; @@ -11,8 +12,10 @@ MultiBlocProvider getBlocProviders({required StatelessWidget child}) { return MultiBlocProvider( providers: [ BlocProvider( - create: (BuildContext context) => - ParametersInputCubit(context.read()), + create: (BuildContext context) => ParametersInputCubit( + context.read(), + context.read(), + ), ), BlocProvider( create: (BuildContext context) => QrGeneratorCubit( diff --git a/lib/di/repositories_providers.dart b/lib/di/repositories_providers.dart index d49c911..9de3dcd 100644 --- a/lib/di/repositories_providers.dart +++ b/lib/di/repositories_providers.dart @@ -1,5 +1,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sol_pay_gen/data/transfer/transfer_request_repository.dart'; +import 'package:sol_pay_gen/validator/keys_validator.dart'; import 'package:sol_pay_gen/validator/number_validator.dart'; MultiRepositoryProvider getRepositoryProviders({ @@ -8,10 +9,13 @@ MultiRepositoryProvider getRepositoryProviders({ return MultiRepositoryProvider( providers: [ RepositoryProvider( - create: (context) => DefaultTransferRequestRepository(), + create: (_) => DefaultTransferRequestRepository(), ), RepositoryProvider( - create: (context) => DefaultNumberValidator(), + create: (_) => DefaultNumberValidator(), + ), + RepositoryProvider( + create: (_) => DefaultKeysValidator(), ) ], child: blocProviders, diff --git a/lib/feature/input/bloc/parameters_input_cubit.dart b/lib/feature/input/bloc/parameters_input_cubit.dart index c2e6cbf..e798ad4 100644 --- a/lib/feature/input/bloc/parameters_input_cubit.dart +++ b/lib/feature/input/bloc/parameters_input_cubit.dart @@ -2,18 +2,22 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sol_pay_gen/data/base/text_value.dart'; import 'package:sol_pay_gen/feature/input/bloc/parameters_input_state.dart'; +import '../../../data/error/input_error.dart'; +import '../../../validator/keys_validator.dart'; import '../../../validator/number_validator.dart'; class ParametersInputCubit extends Cubit { final NumberValidator _numberValidator; + final KeysValidator _keysValidator; ParametersInputCubit( this._numberValidator, + this._keysValidator, ) : super( ParametersInputState( - address: TextValue(text: ""), - amount: TextValue(text: ""), - reference: null, + address: TextValue(), + amount: TextValue(), + reference: TextValue(), memo: null, message: null, label: null, @@ -22,9 +26,12 @@ class ParametersInputCubit extends Cubit { ); void onAddressChange(String address) { - emit( - state.copyWith(address: TextValue(text: address)), - ); + emit(state.copyWith( + address: TextValue( + text: address, + error: _validateAddress(address), + ), + )); } void onAmountChange(String amount) { @@ -45,7 +52,12 @@ class ParametersInputCubit extends Cubit { } void onReferenceChange(String reference) { - emit(state.copyWith(reference: reference)); + emit(state.copyWith( + reference: TextValue( + text: reference, + error: reference.isEmpty ? null : _keysValidator.validateKey(reference), + ), + )); } void onSplTokenChange(String token) { @@ -57,6 +69,16 @@ class ParametersInputCubit extends Cubit { } void onValidate() { - emit(state.validate()); + emit(state.copyWith( + address: state.address.copyWith( + error: _validateAddress(state.address.text), + ), + )); + } + + InputError? _validateAddress(String address) { + return address.isEmpty + ? RequiredAmount() + : _keysValidator.validateKey(address); } } diff --git a/lib/feature/input/bloc/parameters_input_state.dart b/lib/feature/input/bloc/parameters_input_state.dart index fe99384..d9d5b7c 100644 --- a/lib/feature/input/bloc/parameters_input_state.dart +++ b/lib/feature/input/bloc/parameters_input_state.dart @@ -1,11 +1,10 @@ import 'package:equatable/equatable.dart'; import 'package:sol_pay_gen/data/base/text_value.dart'; -import 'package:sol_pay_gen/data/error/input_error.dart'; class ParametersInputState extends Equatable { final TextValue address; final TextValue amount; - final String? reference; + final TextValue reference; final String? label; final String? message; final String? memo; @@ -36,7 +35,7 @@ class ParametersInputState extends Equatable { TextValue? amount, String? label, String? message, - String? reference, + TextValue? reference, String? memo, String? splTokenAddress, }) => @@ -50,13 +49,6 @@ class ParametersInputState extends Equatable { splTokenAddress: splTokenAddress ?? this.splTokenAddress, ); - ParametersInputState validate() { - return copyWith( - address: address.copyWith( - error: address.text.isEmpty ? RequiredAmount() : null, - ), - ); - } - - bool isValid() => amount.isValid() && address.isValid(); + bool isValid() => + amount.isValid() && address.isValid() && reference.isValid(); } diff --git a/lib/feature/input/parameters_input_screen.dart b/lib/feature/input/parameters_input_screen.dart index 16b95ce..55978f4 100644 --- a/lib/feature/input/parameters_input_screen.dart +++ b/lib/feature/input/parameters_input_screen.dart @@ -79,6 +79,7 @@ class InputBody extends StatelessWidget { const Padding(padding: EdgeInsets.only(top: 16.0)), BaseInput( labelText: 'Reference', + error: state.reference.error?.text, onChanged: (address) => context .read() .onReferenceChange(address), diff --git a/lib/feature/qr/bloc/qr_generator_cubit.dart b/lib/feature/qr/bloc/qr_generator_cubit.dart index f81b66e..b6d84a9 100644 --- a/lib/feature/qr/bloc/qr_generator_cubit.dart +++ b/lib/feature/qr/bloc/qr_generator_cubit.dart @@ -26,7 +26,7 @@ class QrGeneratorCubit extends Cubit { label: inputState.label, message: inputState.message, amount: inputState.amount.text, - reference: inputState.reference, + reference: inputState.reference.text, memo: inputState.memo, splToken: inputState.splTokenAddress, ); diff --git a/lib/validator/keys_validator.dart b/lib/validator/keys_validator.dart new file mode 100644 index 0000000..ae7cc5f --- /dev/null +++ b/lib/validator/keys_validator.dart @@ -0,0 +1,27 @@ +import 'package:solana/base58.dart'; + +import '../data/error/input_error.dart'; + +abstract class KeysValidator { + InputError? validateKey(String key); +} + +class DefaultKeysValidator extends KeysValidator { + // https://docs.solana.com/cli/transfer-tokens#:~:text=The%20public%20key%20is%20a,from%2032%20to%2044%20characters. + static const minSolanaKeyLength = 32; + static const maxSolanaKeyLength = 44; + + @override + InputError? validateKey(String key) { + if (key.length < minSolanaKeyLength || key.length > maxSolanaKeyLength) { + return KeyLengthInvalid(); + } else { + try { + base58decode(key); + return null; + } on FormatException catch (_) { + return KeyNotBase58Encoded(); + } + } + } +} diff --git a/pubspec.lock b/pubspec.lock index 903bafd..b0a9240 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bip39: + dependency: transitive + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" bloc: dependency: transitive description: @@ -49,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + borsh_annotation: + dependency: transitive + description: + name: borsh_annotation + sha256: "8c2cc353cb99a12b6c4f9c69e3640d2e18f5127628391658b9fceb96d4fec4d6" + url: "https://pub.dev" + source: hosted + version: "0.3.1+4" characters: dependency: transitive description: @@ -97,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + cryptography: + dependency: transitive + description: + name: cryptography + sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35 + url: "https://pub.dev" + source: hosted + version: "2.5.0" cupertino_icons: dependency: "direct main" description: @@ -105,6 +129,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + decimal: + dependency: transitive + description: + name: decimal + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + ed25519_hd_key: + dependency: transitive + description: + name: ed25519_hd_key + sha256: c5c9f11a03f5789bf9dcd9ae88d641571c802640851f1cacdb13123f171b3a26 + url: "https://pub.dev" + source: hosted + version: "2.2.1" equatable: dependency: "direct main" description: @@ -155,6 +195,14 @@ packages: description: flutter source: sdk version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + url: "https://pub.dev" + source: hosted + version: "2.4.1" frontend_server_client: dependency: transitive description: @@ -171,6 +219,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + http: + dependency: transitive + description: + name: http + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + url: "https://pub.dev" + source: hosted + version: "0.13.6" http_multi_server: dependency: transitive description: @@ -203,6 +267,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" lints: dependency: transitive description: @@ -283,6 +355,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.3" + pinenacl: + dependency: transitive + description: + name: pinenacl + sha256: "3a5503637587d635647c93ea9a8fecf48a420cc7deebe6f1fc85c2a5637ab327" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" pool: dependency: transitive description: @@ -323,6 +411,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + rational: + dependency: transitive + description: + name: rational + sha256: ba58e9e18df9abde280e8b10051e4bce85091e41e8e7e411b6cde2e738d357cf + url: "https://pub.dev" + source: hosted + version: "2.2.2" shelf: dependency: transitive description: @@ -360,6 +456,14 @@ packages: description: flutter source: sdk version: "0.0.99" + solana: + dependency: "direct main" + description: + name: solana + sha256: "24f6e87a28035ce7ce54a488f7476432fc5f9ffccd70547f514a2ec9c6251c87" + url: "https://pub.dev" + source: hosted + version: "0.30.0" source_map_stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 97068b4..5567ddc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: equatable: ^2.0.5 cupertino_icons: ^1.0.2 pretty_qr_code: ^2.0.3 + solana: ^0.30.0 dev_dependencies: flutter_test: