fix(useragent): unsafe, but working implementation of ml-dsa

This commit is contained in:
hdbg
2026-04-07 15:41:50 +02:00
parent 6b8da567dd
commit a4070e7df7
104 changed files with 11133 additions and 461 deletions

View File

@@ -31,7 +31,9 @@ class ClientPickerField extends ConsumerWidget {
? null
: (value) {
ref.read(grantCreationProvider.notifier).setClientId(value);
FormBuilder.of(context)?.fields['walletAccessId']?.didChange(null);
FormBuilder.of(
context,
)?.fields['walletAccessId']?.didChange(null);
},
);
}

View File

@@ -16,46 +16,48 @@ class FormBuilderDateTimeField extends FormBuilderField<DateTime?> {
super.onChanged,
super.validator,
}) : super(
builder: (FormFieldState<DateTime?> field) {
final value = field.value;
return OutlinedButton(
onPressed: () async {
final ctx = field.context;
final now = DateTime.now();
final date = await showDatePicker(
context: ctx,
firstDate: DateTime(now.year - 5),
lastDate: DateTime(now.year + 10),
initialDate: value ?? now,
);
if (date == null) return;
if (!ctx.mounted) return;
final time = await showTimePicker(
context: ctx,
initialTime: TimeOfDay.fromDateTime(value ?? now),
);
if (time == null) return;
field.didChange(DateTime(
date.year,
date.month,
date.day,
time.hour,
time.minute,
));
},
onLongPress: value == null ? null : () => field.didChange(null),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 1.8.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label),
SizedBox(height: 0.6.h),
Text(value?.toLocal().toString() ?? 'Not set'),
],
),
),
);
},
);
builder: (FormFieldState<DateTime?> field) {
final value = field.value;
return OutlinedButton(
onPressed: () async {
final ctx = field.context;
final now = DateTime.now();
final date = await showDatePicker(
context: ctx,
firstDate: DateTime(now.year - 5),
lastDate: DateTime(now.year + 10),
initialDate: value ?? now,
);
if (date == null) return;
if (!ctx.mounted) return;
final time = await showTimePicker(
context: ctx,
initialTime: TimeOfDay.fromDateTime(value ?? now),
);
if (time == null) return;
field.didChange(
DateTime(
date.year,
date.month,
date.day,
time.hour,
time.minute,
),
);
},
onLongPress: value == null ? null : () => field.didChange(null),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 1.8.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label),
SizedBox(height: 0.6.h),
Text(value?.toLocal().toString() ?? 'Not set'),
],
),
),
);
},
);
}

View File

@@ -36,8 +36,8 @@ class WalletAccessPickerField extends ConsumerWidget {
helperText: state.selectedClientId == null
? 'Select a client first'
: accesses.isEmpty
? 'No wallet accesses for this client'
: null,
? 'No wallet accesses for this client'
: null,
border: const OutlineInputBorder(),
),
items: [

View File

@@ -93,9 +93,9 @@ class _EtherTransferForm extends ConsumerWidget {
SizedBox(height: 1.6.h),
Text(
'Ether volume limit',
style: Theme.of(context).textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w800,
),
style: Theme.of(
context,
).textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w800),
),
SizedBox(height: 0.8.h),
Row(
@@ -157,9 +157,9 @@ class _EtherTargetsField extends StatelessWidget {
Expanded(
child: Text(
'Ether targets',
style: Theme.of(context).textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w800,
),
style: Theme.of(
context,
).textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w800),
),
),
TextButton.icon(

View File

@@ -13,7 +13,11 @@ import 'package:sizer/sizer.dart';
part 'token_transfer_grant.g.dart';
class VolumeLimitEntry {
VolumeLimitEntry({required this.id, this.amount = '', this.windowSeconds = ''});
VolumeLimitEntry({
required this.id,
this.amount = '',
this.windowSeconds = '',
});
final int id;
final String amount;
@@ -27,7 +31,6 @@ class VolumeLimitEntry {
);
}
@riverpod
class TokenGrantLimits extends _$TokenGrantLimits {
int _nextId = 0;
@@ -47,7 +50,6 @@ class TokenGrantLimits extends _$TokenGrantLimits {
void remove(int index) => state = [...state]..removeAt(index);
}
class TokenTransferGrantHandler implements GrantFormHandler {
const TokenTransferGrantHandler();
@@ -65,11 +67,16 @@ class TokenTransferGrantHandler implements GrantFormHandler {
return SpecificGrant(
tokenTransfer: TokenTransferSettings(
tokenContract:
parseHexAddress(formValues['tokenContract'] as String? ?? ''),
tokenContract: parseHexAddress(
formValues['tokenContract'] as String? ?? '',
),
target: targetText.trim().isEmpty ? null : parseHexAddress(targetText),
volumeLimits: limits
.where((e) => e.amount.trim().isNotEmpty && e.windowSeconds.trim().isNotEmpty)
.where(
(e) =>
e.amount.trim().isNotEmpty &&
e.windowSeconds.trim().isNotEmpty,
)
.map(
(e) => VolumeRateLimit(
maxVolume: parseBigIntBytes(e.amount),
@@ -153,9 +160,9 @@ class _TokenVolumeLimitsField extends StatelessWidget {
Expanded(
child: Text(
'Token volume limits',
style: Theme.of(context).textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w800,
),
style: Theme.of(
context,
).textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w800),
),
),
TextButton.icon(
@@ -196,7 +203,9 @@ class _TokenVolumeLimitRow extends HookWidget {
@override
Widget build(BuildContext context) {
final amountController = useTextEditingController(text: value.amount);
final windowController = useTextEditingController(text: value.windowSeconds);
final windowController = useTextEditingController(
text: value.windowSeconds,
);
return Row(
children: [
@@ -214,8 +223,7 @@ class _TokenVolumeLimitRow extends HookWidget {
Expanded(
child: TextField(
controller: windowController,
onChanged: (next) =>
onChanged(value.copyWith(windowSeconds: next)),
onChanged: (next) => onChanged(value.copyWith(windowSeconds: next)),
decoration: const InputDecoration(
labelText: 'Window (seconds)',
border: OutlineInputBorder(),

View File

@@ -25,10 +25,10 @@ const _etherHandler = EtherTransferGrantHandler();
const _tokenHandler = TokenTransferGrantHandler();
GrantFormHandler _handlerFor(SpecificGrant_Grant type) => switch (type) {
SpecificGrant_Grant.etherTransfer => _etherHandler,
SpecificGrant_Grant.tokenTransfer => _tokenHandler,
_ => throw ArgumentError('Unsupported grant type: $type'),
};
SpecificGrant_Grant.etherTransfer => _etherHandler,
SpecificGrant_Grant.tokenTransfer => _tokenHandler,
_ => throw ArgumentError('Unsupported grant type: $type'),
};
@RoutePage()
class CreateEvmGrantScreen extends HookConsumerWidget {
@@ -62,12 +62,14 @@ class CreateEvmGrantScreen extends HookConsumerWidget {
);
final validFrom = formValues['validFrom'] as DateTime?;
final validUntil = formValues['validUntil'] as DateTime?;
if (validFrom != null) sharedSettings.validFrom = toTimestamp(validFrom);
if (validFrom != null)
sharedSettings.validFrom = toTimestamp(validFrom);
if (validUntil != null) {
sharedSettings.validUntil = toTimestamp(validUntil);
}
final gasBytes =
optionalBigIntBytes(formValues['maxGasFeePerGas'] as String? ?? '');
final gasBytes = optionalBigIntBytes(
formValues['maxGasFeePerGas'] as String? ?? '',
);
if (gasBytes != null) sharedSettings.maxGasFeePerGas = gasBytes;
final priorityBytes = optionalBigIntBytes(
formValues['maxPriorityFeePerGas'] as String? ?? '',
@@ -106,7 +108,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget {
SizedBox(height: 1.8.h),
const _Section(
title: 'Authorization',
tooltip: 'Select which SDK client receives this grant and '
tooltip:
'Select which SDK client receives this grant and '
'which of its wallet accesses it applies to.',
child: AuthorizationFields(),
),
@@ -118,7 +121,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget {
const Expanded(
child: _Section(
title: 'Chain',
tooltip: 'Restrict this grant to a specific EVM chain ID. '
tooltip:
'Restrict this grant to a specific EVM chain ID. '
'Leave empty to allow any chain.',
optional: true,
child: ChainIdField(),
@@ -128,7 +132,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget {
const Expanded(
child: _Section(
title: 'Timing',
tooltip: 'Set an optional validity window. '
tooltip:
'Set an optional validity window. '
'Signing requests outside this period will be rejected.',
optional: true,
child: ValidityWindowField(),
@@ -145,7 +150,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget {
const Expanded(
child: _Section(
title: 'Gas limits',
tooltip: 'Cap the gas fees this grant may authorize. '
tooltip:
'Cap the gas fees this grant may authorize. '
'Transactions exceeding these values will be rejected.',
optional: true,
child: GasFeeOptionsField(),
@@ -155,7 +161,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget {
const Expanded(
child: _Section(
title: 'Transaction limits',
tooltip: 'Limit how many transactions can be signed '
tooltip:
'Limit how many transactions can be signed '
'within a rolling time window.',
optional: true,
child: TransactionRateLimitField(),
@@ -172,7 +179,8 @@ class CreateEvmGrantScreen extends HookConsumerWidget {
SizedBox(height: 1.8.h),
_Section(
title: 'Grant-specific options',
tooltip: 'Rules specific to the selected transfer type. '
tooltip:
'Rules specific to the selected transfer type. '
'Switch between Ether and token above to change these fields.',
child: handler.buildForm(context, ref),
),
@@ -180,8 +188,7 @@ class CreateEvmGrantScreen extends HookConsumerWidget {
Align(
alignment: Alignment.centerRight,
child: FilledButton.icon(
onPressed:
createMutation is MutationPending ? null : submit,
onPressed: createMutation is MutationPending ? null : submit,
icon: createMutation is MutationPending
? SizedBox(
width: 1.8.h,
@@ -266,9 +273,9 @@ class _Section extends StatelessWidget {
children: [
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w800,
),
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800),
),
SizedBox(width: 0.4.w),
Tooltip(
@@ -283,9 +290,9 @@ class _Section extends StatelessWidget {
SizedBox(width: 0.6.w),
Text(
'(optional)',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: subtleColor,
),
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: subtleColor),
),
],
],

View File

@@ -19,4 +19,3 @@ class AuthorizationFields extends StatelessWidget {
);
}
}

View File

@@ -46,9 +46,7 @@ class GrantCard extends ConsumerWidget {
final accessById = <int, ua_sdk.WalletAccessEntry>{
for (final a in walletAccesses) a.id: a,
};
final walletById = <int, WalletEntry>{
for (final w in wallets) w.id: w,
};
final walletById = <int, WalletEntry>{for (final w in wallets) w.id: w};
final clientNameById = <int, String>{
for (final c in clients) c.id: c.info.name,
};
@@ -192,8 +190,9 @@ class GrantCard extends ConsumerWidget {
padding: EdgeInsets.symmetric(horizontal: 0.8.w),
child: Text(
'·',
style: theme.textTheme.bodySmall
?.copyWith(color: muted),
style: theme.textTheme.bodySmall?.copyWith(
color: muted,
),
),
),
Expanded(
@@ -201,8 +200,9 @@ class GrantCard extends ConsumerWidget {
clientLabel,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall
?.copyWith(color: muted),
style: theme.textTheme.bodySmall?.copyWith(
color: muted,
),
),
),
],