feat(useragent): add SDK clients table screen

This commit is contained in:
hdbg
2026-03-22 13:07:14 +01:00
parent 4ebe7b6fc4
commit d9b3694cab
26 changed files with 1977 additions and 1402 deletions

View File

@@ -0,0 +1,40 @@
import 'package:arbiter/features/connection/evm.dart';
import 'package:arbiter/proto/evm.pb.dart';
import 'package:arbiter/providers/connection/connection_manager.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'evm.g.dart';
@riverpod
class Evm extends _$Evm {
@override
Future<List<WalletEntry>?> build() async {
final connection = await ref.watch(connectionManagerProvider.future);
if (connection == null) {
return null;
}
return listEvmWallets(connection);
}
Future<void> refreshWallets() async {
final connection = await ref.read(connectionManagerProvider.future);
if (connection == null) {
state = const AsyncData(null);
return;
}
state = const AsyncLoading();
state = await AsyncValue.guard(() => listEvmWallets(connection));
}
Future<void> createWallet() async {
final connection = await ref.read(connectionManagerProvider.future);
if (connection == null) {
throw Exception('Not connected to the server.');
}
await createEvmWallet(connection);
state = await AsyncValue.guard(() => listEvmWallets(connection));
}
}

View File

@@ -0,0 +1,55 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'evm.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(Evm)
final evmProvider = EvmProvider._();
final class EvmProvider
extends $AsyncNotifierProvider<Evm, List<WalletEntry>?> {
EvmProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'evmProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$evmHash();
@$internal
@override
Evm create() => Evm();
}
String _$evmHash() => r'f5d05bfa7b820d0b96026a47ca47702a3793af5d';
abstract class _$Evm extends $AsyncNotifier<List<WalletEntry>?> {
FutureOr<List<WalletEntry>?> build();
@$mustCallSuper
@override
void runBuild() {
final ref =
this.ref as $Ref<AsyncValue<List<WalletEntry>?>, List<WalletEntry>?>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<List<WalletEntry>?>, List<WalletEntry>?>,
AsyncValue<List<WalletEntry>?>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}

View File

@@ -0,0 +1,120 @@
import 'package:arbiter/features/connection/evm/grants.dart';
import 'package:arbiter/proto/evm.pb.dart';
import 'package:arbiter/providers/connection/connection_manager.dart';
import 'package:fixnum/fixnum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/experimental/mutation.dart';
import 'package:mtcore/markettakers.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'evm_grants.freezed.dart';
part 'evm_grants.g.dart';
final createEvmGrantMutation = Mutation<int>();
final revokeEvmGrantMutation = Mutation<void>();
@freezed
abstract class EvmGrantsState with _$EvmGrantsState {
const EvmGrantsState._();
const factory EvmGrantsState({
required List<GrantEntry> grants,
@Default(false) bool showRevoked,
}) = _EvmGrantsState;
bool get revokedFilterBackedByServer => false;
}
@riverpod
class EvmGrants extends _$EvmGrants {
@override
Future<EvmGrantsState?> build() async {
final connection = await ref.watch(connectionManagerProvider.future);
if (connection == null) {
return null;
}
try {
final grants = await listEvmGrants(connection);
return EvmGrantsState(grants: grants);
} catch (e, st) {
talker.handle(e, st);
rethrow;
}
}
void toggleShowRevoked(bool value) {
final current = state.asData?.value;
if (current == null) {
return;
}
state = AsyncData(current.copyWith(showRevoked: value));
}
Future<void> refresh() async {
final connection = await ref.read(connectionManagerProvider.future);
if (connection == null) {
state = const AsyncData(null);
return;
}
final previous = state.asData?.value;
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
final grants = await listEvmGrants(connection);
return EvmGrantsState(
grants: grants,
showRevoked: previous?.showRevoked ?? false,
);
});
}
}
Future<int> executeCreateEvmGrant(
MutationTarget ref, {
required int clientId,
required int walletId,
required Int64 chainId,
DateTime? validFrom,
DateTime? validUntil,
List<int>? maxGasFeePerGas,
List<int>? maxPriorityFeePerGas,
TransactionRateLimit? rateLimit,
required SpecificGrant specific,
}) {
return createEvmGrantMutation.run(ref, (tsx) async {
final connection = await tsx.get(connectionManagerProvider.future);
if (connection == null) {
throw Exception('Not connected to the server.');
}
final grantId = await createEvmGrant(
connection,
clientId: clientId,
walletId: walletId,
chainId: chainId,
validFrom: validFrom,
validUntil: validUntil,
maxGasFeePerGas: maxGasFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas,
rateLimit: rateLimit,
specific: specific,
);
await tsx.get(evmGrantsProvider.notifier).refresh();
return grantId;
});
}
Future<void> executeRevokeEvmGrant(MutationTarget ref, {required int grantId}) {
return revokeEvmGrantMutation.run(ref, (tsx) async {
final connection = await tsx.get(connectionManagerProvider.future);
if (connection == null) {
throw Exception('Not connected to the server.');
}
await deleteEvmGrant(connection, grantId);
await tsx.get(evmGrantsProvider.notifier).refresh();
});
}

View File

@@ -0,0 +1,280 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'evm_grants.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$EvmGrantsState {
List<GrantEntry> get grants; bool get showRevoked;
/// Create a copy of EvmGrantsState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$EvmGrantsStateCopyWith<EvmGrantsState> get copyWith => _$EvmGrantsStateCopyWithImpl<EvmGrantsState>(this as EvmGrantsState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is EvmGrantsState&&const DeepCollectionEquality().equals(other.grants, grants)&&(identical(other.showRevoked, showRevoked) || other.showRevoked == showRevoked));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(grants),showRevoked);
@override
String toString() {
return 'EvmGrantsState(grants: $grants, showRevoked: $showRevoked)';
}
}
/// @nodoc
abstract mixin class $EvmGrantsStateCopyWith<$Res> {
factory $EvmGrantsStateCopyWith(EvmGrantsState value, $Res Function(EvmGrantsState) _then) = _$EvmGrantsStateCopyWithImpl;
@useResult
$Res call({
List<GrantEntry> grants, bool showRevoked
});
}
/// @nodoc
class _$EvmGrantsStateCopyWithImpl<$Res>
implements $EvmGrantsStateCopyWith<$Res> {
_$EvmGrantsStateCopyWithImpl(this._self, this._then);
final EvmGrantsState _self;
final $Res Function(EvmGrantsState) _then;
/// Create a copy of EvmGrantsState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? grants = null,Object? showRevoked = null,}) {
return _then(_self.copyWith(
grants: null == grants ? _self.grants : grants // ignore: cast_nullable_to_non_nullable
as List<GrantEntry>,showRevoked: null == showRevoked ? _self.showRevoked : showRevoked // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [EvmGrantsState].
extension EvmGrantsStatePatterns on EvmGrantsState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _EvmGrantsState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _EvmGrantsState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _EvmGrantsState value) $default,){
final _that = this;
switch (_that) {
case _EvmGrantsState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _EvmGrantsState value)? $default,){
final _that = this;
switch (_that) {
case _EvmGrantsState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<GrantEntry> grants, bool showRevoked)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _EvmGrantsState() when $default != null:
return $default(_that.grants,_that.showRevoked);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<GrantEntry> grants, bool showRevoked) $default,) {final _that = this;
switch (_that) {
case _EvmGrantsState():
return $default(_that.grants,_that.showRevoked);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<GrantEntry> grants, bool showRevoked)? $default,) {final _that = this;
switch (_that) {
case _EvmGrantsState() when $default != null:
return $default(_that.grants,_that.showRevoked);case _:
return null;
}
}
}
/// @nodoc
class _EvmGrantsState extends EvmGrantsState {
const _EvmGrantsState({required final List<GrantEntry> grants, this.showRevoked = false}): _grants = grants,super._();
final List<GrantEntry> _grants;
@override List<GrantEntry> get grants {
if (_grants is EqualUnmodifiableListView) return _grants;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_grants);
}
@override@JsonKey() final bool showRevoked;
/// Create a copy of EvmGrantsState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$EvmGrantsStateCopyWith<_EvmGrantsState> get copyWith => __$EvmGrantsStateCopyWithImpl<_EvmGrantsState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _EvmGrantsState&&const DeepCollectionEquality().equals(other._grants, _grants)&&(identical(other.showRevoked, showRevoked) || other.showRevoked == showRevoked));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_grants),showRevoked);
@override
String toString() {
return 'EvmGrantsState(grants: $grants, showRevoked: $showRevoked)';
}
}
/// @nodoc
abstract mixin class _$EvmGrantsStateCopyWith<$Res> implements $EvmGrantsStateCopyWith<$Res> {
factory _$EvmGrantsStateCopyWith(_EvmGrantsState value, $Res Function(_EvmGrantsState) _then) = __$EvmGrantsStateCopyWithImpl;
@override @useResult
$Res call({
List<GrantEntry> grants, bool showRevoked
});
}
/// @nodoc
class __$EvmGrantsStateCopyWithImpl<$Res>
implements _$EvmGrantsStateCopyWith<$Res> {
__$EvmGrantsStateCopyWithImpl(this._self, this._then);
final _EvmGrantsState _self;
final $Res Function(_EvmGrantsState) _then;
/// Create a copy of EvmGrantsState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? grants = null,Object? showRevoked = null,}) {
return _then(_EvmGrantsState(
grants: null == grants ? _self._grants : grants // ignore: cast_nullable_to_non_nullable
as List<GrantEntry>,showRevoked: null == showRevoked ? _self.showRevoked : showRevoked // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View File

@@ -0,0 +1,54 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'evm_grants.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(EvmGrants)
final evmGrantsProvider = EvmGrantsProvider._();
final class EvmGrantsProvider
extends $AsyncNotifierProvider<EvmGrants, EvmGrantsState?> {
EvmGrantsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'evmGrantsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$evmGrantsHash();
@$internal
@override
EvmGrants create() => EvmGrants();
}
String _$evmGrantsHash() => r'd71ec12bbc1b412f11fdbaae27382b289f8a3538';
abstract class _$EvmGrants extends $AsyncNotifier<EvmGrantsState?> {
FutureOr<EvmGrantsState?> build();
@$mustCallSuper
@override
void runBuild() {
final ref = this.ref as $Ref<AsyncValue<EvmGrantsState?>, EvmGrantsState?>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<AsyncValue<EvmGrantsState?>, EvmGrantsState?>,
AsyncValue<EvmGrantsState?>,
Object?,
Object?
>;
element.handleCreate(ref, build);
}
}