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

@@ -30,7 +30,8 @@ message SdkClientRevokeRequest {
message SdkClientEntry {
int32 id = 1;
bytes pubkey = 2;
int32 created_at = 3;
arbiter.client.ClientInfo info = 3;
int32 created_at = 4;
}
message SdkClientList {

View File

@@ -25,6 +25,19 @@ pub enum Error {
Internal { message: Cow<'static, str> },
}
impl From<crate::db::PoolError> for Error {
fn from(err: crate::db::PoolError) -> Self {
error!(?err, "Database pool error");
Self::internal("Database pool error")
}
}
impl From<diesel::result::Error> for Error {
fn from(err: diesel::result::Error) -> Self {
error!(?err, "Database error");
Self::internal("Database error")
}
}
impl Error {
pub fn internal(message: impl Into<Cow<'static, str>>) -> Self {
Self::Internal {
@@ -49,7 +62,7 @@ mod connection;
pub(crate) use connection::{
BootstrapError, HandleBootstrapEncryptedKey, HandleEvmWalletCreate, HandleEvmWalletList,
HandleGrantCreate, HandleGrantDelete, HandleGrantList, HandleNewClientApprove,
HandleQueryVaultState,
HandleQueryVaultState, HandleSdkClientList,
};
pub use connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError};

View File

@@ -2,15 +2,18 @@ use std::sync::Mutex;
use alloy::primitives::Address;
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
use diesel::{QueryDsl as _, SelectableHelper};
use diesel_async::RunQueryDsl;
use kameo::error::SendError;
use kameo::messages;
use kameo::prelude::Context;
use kameo::{message, messages};
use tracing::{error, info};
use x25519_dalek::{EphemeralSecret, PublicKey};
use crate::actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer;
use crate::actors::keyholder::KeyHolderState;
use crate::actors::user_agent::session::Error;
use crate::db::models::{ProgramClient, ProgramClientMetadata};
use crate::evm::policies::{Grant, SpecificGrant};
use crate::safe_cell::SafeCell;
use crate::{
@@ -383,4 +386,20 @@ impl UserAgentSession {
Ok(())
}
#[message]
pub(crate) async fn handle_sdk_client_list(
&mut self,
) -> Result<Vec<(ProgramClient, ProgramClientMetadata)>, Error> {
use crate::db::schema::{program_client, client_metadata};
let mut conn = self.props.db.get().await?;
let clients = program_client::table
.inner_join(client_metadata::table)
.select((ProgramClient::as_select(), ProgramClientMetadata::as_select()))
.load::<(ProgramClient, ProgramClientMetadata)>(&mut conn)
.await?;
Ok(clients)
}
}

View File

@@ -21,10 +21,14 @@ use arbiter_proto::{
user_agent::{
BootstrapEncryptedKey as ProtoBootstrapEncryptedKey,
BootstrapResult as ProtoBootstrapResult,
SdkClientEntry as ProtoSdkClientEntry, SdkClientError as ProtoSdkClientError,
SdkClientConnectionCancel as ProtoSdkClientConnectionCancel,
SdkClientConnectionRequest as ProtoSdkClientConnectionRequest,
SdkClientList as ProtoSdkClientList,
SdkClientListResponse as ProtoSdkClientListResponse,
UnsealEncryptedKey as ProtoUnsealEncryptedKey, UnsealResult as ProtoUnsealResult,
UnsealStart, UserAgentRequest, UserAgentResponse, VaultState as ProtoVaultState,
sdk_client_list_response::Result as ProtoSdkClientListResult,
user_agent_request::Payload as UserAgentRequestPayload,
user_agent_response::Payload as UserAgentResponsePayload,
},
@@ -49,7 +53,8 @@ use crate::{
session::{
BootstrapError, Error, HandleBootstrapEncryptedKey, HandleEvmWalletCreate,
HandleEvmWalletList, HandleGrantCreate, HandleGrantDelete, HandleGrantList,
HandleNewClientApprove, HandleQueryVaultState, HandleUnsealEncryptedKey,
HandleNewClientApprove, HandleQueryVaultState, HandleSdkClientList,
HandleUnsealEncryptedKey,
HandleUnsealRequest, UnsealError,
},
},
@@ -303,7 +308,11 @@ async fn dispatch_conn_message(
return Ok(());
}
UserAgentRequestPayload::SdkClientRevoke(_sdk_client_revoke_request) => todo!(),
UserAgentRequestPayload::SdkClientList(_) => todo!(),
UserAgentRequestPayload::SdkClientList(_) => {
UserAgentResponsePayload::SdkClientListResponse(
SdkClient::list_response(actor.ask(HandleSdkClientList {}).await),
)
},
UserAgentRequestPayload::AuthChallengeRequest(..)
| UserAgentRequestPayload::AuthChallengeSolution(..) => {
warn!(?payload, "Unsupported post-auth user agent request");
@@ -355,6 +364,43 @@ async fn send_out_of_band(
.map_err(|_| ())
}
struct SdkClient;
impl SdkClient {
fn list_response<M>(
result: Result<
Vec<(crate::db::models::ProgramClient, crate::db::models::ProgramClientMetadata)>,
SendError<M, Error>,
>,
) -> ProtoSdkClientListResponse {
let result = match result {
Ok(clients) => ProtoSdkClientListResult::Clients(ProtoSdkClientList {
clients: clients
.into_iter()
.map(|(client, metadata)| ProtoSdkClientEntry {
id: client.id,
pubkey: client.public_key,
info: Some(ProtoClientMetadata {
name: metadata.name,
description: metadata.description,
version: metadata.version,
}),
created_at: client.created_at.0.timestamp() as i32,
})
.collect(),
}),
Err(err) => {
warn!(error = ?err, "Failed to list SDK clients");
ProtoSdkClientListResult::Error(ProtoSdkClientError::Internal.into())
}
};
ProtoSdkClientListResponse {
result: Some(result),
}
}
}
fn parse_grant_request(
shared: Option<ProtoSharedSettings>,
specific: Option<ProtoSpecificGrant>,

View File

@@ -4,14 +4,8 @@ import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:fixnum/fixnum.dart';
import 'package:protobuf/well_known_types/google/protobuf/timestamp.pb.dart';
Future<List<GrantEntry>> listEvmGrants(
Connection connection, {
int? walletId,
}) async {
Future<List<GrantEntry>> listEvmGrants(Connection connection) async {
final request = EvmGrantListRequest();
if (walletId != null) {
request.walletId = walletId;
}
final response = await connection.request(
UserAgentRequest(evmGrantList: request),
@@ -45,38 +39,7 @@ Future<int> createEvmGrant(
TransactionRateLimit? rateLimit,
required SpecificGrant specific,
}) async {
final response = await connection.request(
UserAgentRequest(
evmGrantCreate: EvmGrantCreateRequest(
clientId: clientId,
shared: SharedSettings(
walletId: walletId,
chainId: chainId,
validFrom: validFrom == null ? null : _toTimestamp(validFrom),
validUntil: validUntil == null ? null : _toTimestamp(validUntil),
maxGasFeePerGas: maxGasFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas,
rateLimit: rateLimit,
),
specific: specific,
),
),
);
if (!response.hasEvmGrantCreate()) {
throw Exception(
'Expected EVM grant create response, got ${response.whichPayload()}',
);
}
final result = response.evmGrantCreate;
switch (result.whichResult()) {
case EvmGrantCreateResponse_Result.grantId:
return result.grantId;
case EvmGrantCreateResponse_Result.error:
throw Exception(_describeGrantError(result.error));
case EvmGrantCreateResponse_Result.notSet:
throw Exception('Grant creation returned no result.');
}
throw UnimplementedError('EVM grant creation is not yet implemented.');
}
Future<void> deleteEvmGrant(Connection connection, int grantId) async {

View File

@@ -22,12 +22,91 @@ export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
export 'client.pbenum.dart';
class ClientInfo extends $pb.GeneratedMessage {
factory ClientInfo({
$core.String? name,
$core.String? description,
$core.String? version,
}) {
final result = create();
if (name != null) result.name = name;
if (description != null) result.description = description;
if (version != null) result.version = version;
return result;
}
ClientInfo._();
factory ClientInfo.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory ClientInfo.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'ClientInfo',
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'name')
..aOS(2, _omitFieldNames ? '' : 'description')
..aOS(3, _omitFieldNames ? '' : 'version')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ClientInfo clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
ClientInfo copyWith(void Function(ClientInfo) updates) =>
super.copyWith((message) => updates(message as ClientInfo)) as ClientInfo;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static ClientInfo create() => ClientInfo._();
@$core.override
ClientInfo createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static ClientInfo getDefault() => _defaultInstance ??=
$pb.GeneratedMessage.$_defaultFor<ClientInfo>(create);
static ClientInfo? _defaultInstance;
@$pb.TagNumber(1)
$core.String get name => $_getSZ(0);
@$pb.TagNumber(1)
set name($core.String value) => $_setString(0, value);
@$pb.TagNumber(1)
$core.bool hasName() => $_has(0);
@$pb.TagNumber(1)
void clearName() => $_clearField(1);
@$pb.TagNumber(2)
$core.String get description => $_getSZ(1);
@$pb.TagNumber(2)
set description($core.String value) => $_setString(1, value);
@$pb.TagNumber(2)
$core.bool hasDescription() => $_has(1);
@$pb.TagNumber(2)
void clearDescription() => $_clearField(2);
@$pb.TagNumber(3)
$core.String get version => $_getSZ(2);
@$pb.TagNumber(3)
set version($core.String value) => $_setString(2, value);
@$pb.TagNumber(3)
$core.bool hasVersion() => $_has(2);
@$pb.TagNumber(3)
void clearVersion() => $_clearField(3);
}
class AuthChallengeRequest extends $pb.GeneratedMessage {
factory AuthChallengeRequest({
$core.List<$core.int>? pubkey,
ClientInfo? clientInfo,
}) {
final result = create();
if (pubkey != null) result.pubkey = pubkey;
if (clientInfo != null) result.clientInfo = clientInfo;
return result;
}
@@ -46,6 +125,8 @@ class AuthChallengeRequest extends $pb.GeneratedMessage {
createEmptyInstance: create)
..a<$core.List<$core.int>>(
1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY)
..aOM<ClientInfo>(2, _omitFieldNames ? '' : 'clientInfo',
subBuilder: ClientInfo.create)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@@ -75,6 +156,17 @@ class AuthChallengeRequest extends $pb.GeneratedMessage {
$core.bool hasPubkey() => $_has(0);
@$pb.TagNumber(1)
void clearPubkey() => $_clearField(1);
@$pb.TagNumber(2)
ClientInfo get clientInfo => $_getN(1);
@$pb.TagNumber(2)
set clientInfo(ClientInfo value) => $_setField(2, value);
@$pb.TagNumber(2)
$core.bool hasClientInfo() => $_has(1);
@$pb.TagNumber(2)
void clearClientInfo() => $_clearField(2);
@$pb.TagNumber(2)
ClientInfo ensureClientInfo() => $_ensure(1);
}
class AuthChallenge extends $pb.GeneratedMessage {

View File

@@ -55,18 +55,62 @@ final $typed_data.Uint8List vaultStateDescriptor = $convert.base64Decode(
'VfVU5CT09UU1RSQVBQRUQQARIWChJWQVVMVF9TVEFURV9TRUFMRUQQAhIYChRWQVVMVF9TVEFU'
'RV9VTlNFQUxFRBADEhUKEVZBVUxUX1NUQVRFX0VSUk9SEAQ=');
@$core.Deprecated('Use clientInfoDescriptor instead')
const ClientInfo$json = {
'1': 'ClientInfo',
'2': [
{'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
{
'1': 'description',
'3': 2,
'4': 1,
'5': 9,
'9': 0,
'10': 'description',
'17': true
},
{
'1': 'version',
'3': 3,
'4': 1,
'5': 9,
'9': 1,
'10': 'version',
'17': true
},
],
'8': [
{'1': '_description'},
{'1': '_version'},
],
};
/// Descriptor for `ClientInfo`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List clientInfoDescriptor = $convert.base64Decode(
'CgpDbGllbnRJbmZvEhIKBG5hbWUYASABKAlSBG5hbWUSJQoLZGVzY3JpcHRpb24YAiABKAlIAF'
'ILZGVzY3JpcHRpb26IAQESHQoHdmVyc2lvbhgDIAEoCUgBUgd2ZXJzaW9uiAEBQg4KDF9kZXNj'
'cmlwdGlvbkIKCghfdmVyc2lvbg==');
@$core.Deprecated('Use authChallengeRequestDescriptor instead')
const AuthChallengeRequest$json = {
'1': 'AuthChallengeRequest',
'2': [
{'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'},
{
'1': 'client_info',
'3': 2,
'4': 1,
'5': 11,
'6': '.arbiter.client.ClientInfo',
'10': 'clientInfo'
},
],
};
/// Descriptor for `AuthChallengeRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List authChallengeRequestDescriptor =
$convert.base64Decode(
'ChRBdXRoQ2hhbGxlbmdlUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleQ==');
final $typed_data.Uint8List authChallengeRequestDescriptor = $convert.base64Decode(
'ChRBdXRoQ2hhbGxlbmdlUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleRI7CgtjbGllbn'
'RfaW5mbxgCIAEoCzIaLmFyYml0ZXIuY2xpZW50LkNsaWVudEluZm9SCmNsaWVudEluZm8=');
@$core.Deprecated('Use authChallengeDescriptor instead')
const AuthChallenge$json = {

View File

@@ -436,7 +436,7 @@ class VolumeRateLimit extends $pb.GeneratedMessage {
class SharedSettings extends $pb.GeneratedMessage {
factory SharedSettings({
$core.int? walletId,
$core.int? walletAccessId,
$fixnum.Int64? chainId,
$0.Timestamp? validFrom,
$0.Timestamp? validUntil,
@@ -445,7 +445,7 @@ class SharedSettings extends $pb.GeneratedMessage {
TransactionRateLimit? rateLimit,
}) {
final result = create();
if (walletId != null) result.walletId = walletId;
if (walletAccessId != null) result.walletAccessId = walletAccessId;
if (chainId != null) result.chainId = chainId;
if (validFrom != null) result.validFrom = validFrom;
if (validUntil != null) result.validUntil = validUntil;
@@ -469,7 +469,7 @@ class SharedSettings extends $pb.GeneratedMessage {
_omitMessageNames ? '' : 'SharedSettings',
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
createEmptyInstance: create)
..aI(1, _omitFieldNames ? '' : 'walletId')
..aI(1, _omitFieldNames ? '' : 'walletAccessId')
..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'chainId', $pb.PbFieldType.OU6,
defaultOrMaker: $fixnum.Int64.ZERO)
..aOM<$0.Timestamp>(3, _omitFieldNames ? '' : 'validFrom',
@@ -504,13 +504,13 @@ class SharedSettings extends $pb.GeneratedMessage {
static SharedSettings? _defaultInstance;
@$pb.TagNumber(1)
$core.int get walletId => $_getIZ(0);
$core.int get walletAccessId => $_getIZ(0);
@$pb.TagNumber(1)
set walletId($core.int value) => $_setSignedInt32(0, value);
set walletAccessId($core.int value) => $_setSignedInt32(0, value);
@$pb.TagNumber(1)
$core.bool hasWalletId() => $_has(0);
$core.bool hasWalletAccessId() => $_has(0);
@$pb.TagNumber(1)
void clearWalletId() => $_clearField(1);
void clearWalletAccessId() => $_clearField(1);
@$pb.TagNumber(2)
$fixnum.Int64 get chainId => $_getI64(1);
@@ -1625,12 +1625,10 @@ class TransactionEvalError extends $pb.GeneratedMessage {
/// --- UserAgent grant management ---
class EvmGrantCreateRequest extends $pb.GeneratedMessage {
factory EvmGrantCreateRequest({
$core.int? clientId,
SharedSettings? shared,
SpecificGrant? specific,
}) {
final result = create();
if (clientId != null) result.clientId = clientId;
if (shared != null) result.shared = shared;
if (specific != null) result.specific = specific;
return result;
@@ -1649,10 +1647,9 @@ class EvmGrantCreateRequest extends $pb.GeneratedMessage {
_omitMessageNames ? '' : 'EvmGrantCreateRequest',
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
createEmptyInstance: create)
..aI(1, _omitFieldNames ? '' : 'clientId')
..aOM<SharedSettings>(2, _omitFieldNames ? '' : 'shared',
..aOM<SharedSettings>(1, _omitFieldNames ? '' : 'shared',
subBuilder: SharedSettings.create)
..aOM<SpecificGrant>(3, _omitFieldNames ? '' : 'specific',
..aOM<SpecificGrant>(2, _omitFieldNames ? '' : 'specific',
subBuilder: SpecificGrant.create)
..hasRequiredFields = false;
@@ -1677,35 +1674,26 @@ class EvmGrantCreateRequest extends $pb.GeneratedMessage {
static EvmGrantCreateRequest? _defaultInstance;
@$pb.TagNumber(1)
$core.int get clientId => $_getIZ(0);
SharedSettings get shared => $_getN(0);
@$pb.TagNumber(1)
set clientId($core.int value) => $_setSignedInt32(0, value);
set shared(SharedSettings value) => $_setField(1, value);
@$pb.TagNumber(1)
$core.bool hasClientId() => $_has(0);
$core.bool hasShared() => $_has(0);
@$pb.TagNumber(1)
void clearClientId() => $_clearField(1);
void clearShared() => $_clearField(1);
@$pb.TagNumber(1)
SharedSettings ensureShared() => $_ensure(0);
@$pb.TagNumber(2)
SharedSettings get shared => $_getN(1);
SpecificGrant get specific => $_getN(1);
@$pb.TagNumber(2)
set shared(SharedSettings value) => $_setField(2, value);
set specific(SpecificGrant value) => $_setField(2, value);
@$pb.TagNumber(2)
$core.bool hasShared() => $_has(1);
$core.bool hasSpecific() => $_has(1);
@$pb.TagNumber(2)
void clearShared() => $_clearField(2);
void clearSpecific() => $_clearField(2);
@$pb.TagNumber(2)
SharedSettings ensureShared() => $_ensure(1);
@$pb.TagNumber(3)
SpecificGrant get specific => $_getN(2);
@$pb.TagNumber(3)
set specific(SpecificGrant value) => $_setField(3, value);
@$pb.TagNumber(3)
$core.bool hasSpecific() => $_has(2);
@$pb.TagNumber(3)
void clearSpecific() => $_clearField(3);
@$pb.TagNumber(3)
SpecificGrant ensureSpecific() => $_ensure(2);
SpecificGrant ensureSpecific() => $_ensure(1);
}
enum EvmGrantCreateResponse_Result { grantId, error, notSet }
@@ -1939,13 +1927,13 @@ class EvmGrantDeleteResponse extends $pb.GeneratedMessage {
class GrantEntry extends $pb.GeneratedMessage {
factory GrantEntry({
$core.int? id,
$core.int? clientId,
$core.int? walletAccessId,
SharedSettings? shared,
SpecificGrant? specific,
}) {
final result = create();
if (id != null) result.id = id;
if (clientId != null) result.clientId = clientId;
if (walletAccessId != null) result.walletAccessId = walletAccessId;
if (shared != null) result.shared = shared;
if (specific != null) result.specific = specific;
return result;
@@ -1965,7 +1953,7 @@ class GrantEntry extends $pb.GeneratedMessage {
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
createEmptyInstance: create)
..aI(1, _omitFieldNames ? '' : 'id')
..aI(2, _omitFieldNames ? '' : 'clientId')
..aI(2, _omitFieldNames ? '' : 'walletAccessId')
..aOM<SharedSettings>(3, _omitFieldNames ? '' : 'shared',
subBuilder: SharedSettings.create)
..aOM<SpecificGrant>(4, _omitFieldNames ? '' : 'specific',
@@ -2000,13 +1988,13 @@ class GrantEntry extends $pb.GeneratedMessage {
void clearId() => $_clearField(1);
@$pb.TagNumber(2)
$core.int get clientId => $_getIZ(1);
$core.int get walletAccessId => $_getIZ(1);
@$pb.TagNumber(2)
set clientId($core.int value) => $_setSignedInt32(1, value);
set walletAccessId($core.int value) => $_setSignedInt32(1, value);
@$pb.TagNumber(2)
$core.bool hasClientId() => $_has(1);
$core.bool hasWalletAccessId() => $_has(1);
@$pb.TagNumber(2)
void clearClientId() => $_clearField(2);
void clearWalletAccessId() => $_clearField(2);
@$pb.TagNumber(3)
SharedSettings get shared => $_getN(2);
@@ -2033,10 +2021,10 @@ class GrantEntry extends $pb.GeneratedMessage {
class EvmGrantListRequest extends $pb.GeneratedMessage {
factory EvmGrantListRequest({
$core.int? walletId,
$core.int? walletAccessId,
}) {
final result = create();
if (walletId != null) result.walletId = walletId;
if (walletAccessId != null) result.walletAccessId = walletAccessId;
return result;
}
@@ -2053,7 +2041,7 @@ class EvmGrantListRequest extends $pb.GeneratedMessage {
_omitMessageNames ? '' : 'EvmGrantListRequest',
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
createEmptyInstance: create)
..aI(1, _omitFieldNames ? '' : 'walletId')
..aI(1, _omitFieldNames ? '' : 'walletAccessId')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@@ -2076,13 +2064,13 @@ class EvmGrantListRequest extends $pb.GeneratedMessage {
static EvmGrantListRequest? _defaultInstance;
@$pb.TagNumber(1)
$core.int get walletId => $_getIZ(0);
$core.int get walletAccessId => $_getIZ(0);
@$pb.TagNumber(1)
set walletId($core.int value) => $_setSignedInt32(0, value);
set walletAccessId($core.int value) => $_setSignedInt32(0, value);
@$pb.TagNumber(1)
$core.bool hasWalletId() => $_has(0);
$core.bool hasWalletAccessId() => $_has(0);
@$pb.TagNumber(1)
void clearWalletId() => $_clearField(1);
void clearWalletAccessId() => $_clearField(1);
}
enum EvmGrantListResponse_Result { grants, error, notSet }

View File

@@ -162,7 +162,7 @@ final $typed_data.Uint8List volumeRateLimitDescriptor = $convert.base64Decode(
const SharedSettings$json = {
'1': 'SharedSettings',
'2': [
{'1': 'wallet_id', '3': 1, '4': 1, '5': 5, '10': 'walletId'},
{'1': 'wallet_access_id', '3': 1, '4': 1, '5': 5, '10': 'walletAccessId'},
{'1': 'chain_id', '3': 2, '4': 1, '5': 4, '10': 'chainId'},
{
'1': 'valid_from',
@@ -224,15 +224,15 @@ const SharedSettings$json = {
/// Descriptor for `SharedSettings`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sharedSettingsDescriptor = $convert.base64Decode(
'Cg5TaGFyZWRTZXR0aW5ncxIbCgl3YWxsZXRfaWQYASABKAVSCHdhbGxldElkEhkKCGNoYWluX2'
'lkGAIgASgEUgdjaGFpbklkEj4KCnZhbGlkX2Zyb20YAyABKAsyGi5nb29nbGUucHJvdG9idWYu'
'VGltZXN0YW1wSABSCXZhbGlkRnJvbYgBARJACgt2YWxpZF91bnRpbBgEIAEoCzIaLmdvb2dsZS'
'5wcm90b2J1Zi5UaW1lc3RhbXBIAVIKdmFsaWRVbnRpbIgBARIxChNtYXhfZ2FzX2ZlZV9wZXJf'
'Z2FzGAUgASgMSAJSD21heEdhc0ZlZVBlckdhc4gBARI7ChhtYXhfcHJpb3JpdHlfZmVlX3Blcl'
'9nYXMYBiABKAxIA1IUbWF4UHJpb3JpdHlGZWVQZXJHYXOIAQESRQoKcmF0ZV9saW1pdBgHIAEo'
'CzIhLmFyYml0ZXIuZXZtLlRyYW5zYWN0aW9uUmF0ZUxpbWl0SARSCXJhdGVMaW1pdIgBAUINCg'
'tfdmFsaWRfZnJvbUIOCgxfdmFsaWRfdW50aWxCFgoUX21heF9nYXNfZmVlX3Blcl9nYXNCGwoZ'
'X21heF9wcmlvcml0eV9mZWVfcGVyX2dhc0INCgtfcmF0ZV9saW1pdA==');
'Cg5TaGFyZWRTZXR0aW5ncxIoChB3YWxsZXRfYWNjZXNzX2lkGAEgASgFUg53YWxsZXRBY2Nlc3'
'NJZBIZCghjaGFpbl9pZBgCIAEoBFIHY2hhaW5JZBI+Cgp2YWxpZF9mcm9tGAMgASgLMhouZ29v'
'Z2xlLnByb3RvYnVmLlRpbWVzdGFtcEgAUgl2YWxpZEZyb22IAQESQAoLdmFsaWRfdW50aWwYBC'
'ABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wSAFSCnZhbGlkVW50aWyIAQESMQoTbWF4'
'X2dhc19mZWVfcGVyX2dhcxgFIAEoDEgCUg9tYXhHYXNGZWVQZXJHYXOIAQESOwoYbWF4X3ByaW'
'9yaXR5X2ZlZV9wZXJfZ2FzGAYgASgMSANSFG1heFByaW9yaXR5RmVlUGVyR2FziAEBEkUKCnJh'
'dGVfbGltaXQYByABKAsyIS5hcmJpdGVyLmV2bS5UcmFuc2FjdGlvblJhdGVMaW1pdEgEUglyYX'
'RlTGltaXSIAQFCDQoLX3ZhbGlkX2Zyb21CDgoMX3ZhbGlkX3VudGlsQhYKFF9tYXhfZ2FzX2Zl'
'ZV9wZXJfZ2FzQhsKGV9tYXhfcHJpb3JpdHlfZmVlX3Blcl9nYXNCDQoLX3JhdGVfbGltaXQ=');
@$core.Deprecated('Use etherTransferSettingsDescriptor instead')
const EtherTransferSettings$json = {
@@ -631,10 +631,9 @@ final $typed_data.Uint8List transactionEvalErrorDescriptor = $convert.base64Deco
const EvmGrantCreateRequest$json = {
'1': 'EvmGrantCreateRequest',
'2': [
{'1': 'client_id', '3': 1, '4': 1, '5': 5, '10': 'clientId'},
{
'1': 'shared',
'3': 2,
'3': 1,
'4': 1,
'5': 11,
'6': '.arbiter.evm.SharedSettings',
@@ -642,7 +641,7 @@ const EvmGrantCreateRequest$json = {
},
{
'1': 'specific',
'3': 3,
'3': 2,
'4': 1,
'5': 11,
'6': '.arbiter.evm.SpecificGrant',
@@ -653,9 +652,9 @@ const EvmGrantCreateRequest$json = {
/// Descriptor for `EvmGrantCreateRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List evmGrantCreateRequestDescriptor = $convert.base64Decode(
'ChVFdm1HcmFudENyZWF0ZVJlcXVlc3QSGwoJY2xpZW50X2lkGAEgASgFUghjbGllbnRJZBIzCg'
'ZzaGFyZWQYAiABKAsyGy5hcmJpdGVyLmV2bS5TaGFyZWRTZXR0aW5nc1IGc2hhcmVkEjYKCHNw'
'ZWNpZmljGAMgASgLMhouYXJiaXRlci5ldm0uU3BlY2lmaWNHcmFudFIIc3BlY2lmaWM=');
'ChVFdm1HcmFudENyZWF0ZVJlcXVlc3QSMwoGc2hhcmVkGAEgASgLMhsuYXJiaXRlci5ldm0uU2'
'hhcmVkU2V0dGluZ3NSBnNoYXJlZBI2CghzcGVjaWZpYxgCIAEoCzIaLmFyYml0ZXIuZXZtLlNw'
'ZWNpZmljR3JhbnRSCHNwZWNpZmlj');
@$core.Deprecated('Use evmGrantCreateResponseDescriptor instead')
const EvmGrantCreateResponse$json = {
@@ -734,7 +733,7 @@ const GrantEntry$json = {
'1': 'GrantEntry',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 5, '10': 'id'},
{'1': 'client_id', '3': 2, '4': 1, '5': 5, '10': 'clientId'},
{'1': 'wallet_access_id', '3': 2, '4': 1, '5': 5, '10': 'walletAccessId'},
{
'1': 'shared',
'3': 3,
@@ -756,34 +755,34 @@ const GrantEntry$json = {
/// Descriptor for `GrantEntry`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List grantEntryDescriptor = $convert.base64Decode(
'CgpHcmFudEVudHJ5Eg4KAmlkGAEgASgFUgJpZBIbCgljbGllbnRfaWQYAiABKAVSCGNsaWVudE'
'lkEjMKBnNoYXJlZBgDIAEoCzIbLmFyYml0ZXIuZXZtLlNoYXJlZFNldHRpbmdzUgZzaGFyZWQS'
'NgoIc3BlY2lmaWMYBCABKAsyGi5hcmJpdGVyLmV2bS5TcGVjaWZpY0dyYW50UghzcGVjaWZpYw'
'==');
'CgpHcmFudEVudHJ5Eg4KAmlkGAEgASgFUgJpZBIoChB3YWxsZXRfYWNjZXNzX2lkGAIgASgFUg'
'53YWxsZXRBY2Nlc3NJZBIzCgZzaGFyZWQYAyABKAsyGy5hcmJpdGVyLmV2bS5TaGFyZWRTZXR0'
'aW5nc1IGc2hhcmVkEjYKCHNwZWNpZmljGAQgASgLMhouYXJiaXRlci5ldm0uU3BlY2lmaWNHcm'
'FudFIIc3BlY2lmaWM=');
@$core.Deprecated('Use evmGrantListRequestDescriptor instead')
const EvmGrantListRequest$json = {
'1': 'EvmGrantListRequest',
'2': [
{
'1': 'wallet_id',
'1': 'wallet_access_id',
'3': 1,
'4': 1,
'5': 5,
'9': 0,
'10': 'walletId',
'10': 'walletAccessId',
'17': true
},
],
'8': [
{'1': '_wallet_id'},
{'1': '_wallet_access_id'},
],
};
/// Descriptor for `EvmGrantListRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List evmGrantListRequestDescriptor = $convert.base64Decode(
'ChNFdm1HcmFudExpc3RSZXF1ZXN0EiAKCXdhbGxldF9pZBgBIAEoBUgAUgh3YWxsZXRJZIgBAU'
'IMCgpfd2FsbGV0X2lk');
'ChNFdm1HcmFudExpc3RSZXF1ZXN0Ei0KEHdhbGxldF9hY2Nlc3NfaWQYASABKAVIAFIOd2FsbG'
'V0QWNjZXNzSWSIAQFCEwoRX3dhbGxldF9hY2Nlc3NfaWQ=');
@$core.Deprecated('Use evmGrantListResponseDescriptor instead')
const EvmGrantListResponse$json = {

File diff suppressed because it is too large Load Diff

View File

@@ -39,6 +39,36 @@ class KeyType extends $pb.ProtobufEnum {
const KeyType._(super.value, super.name);
}
class SdkClientError extends $pb.ProtobufEnum {
static const SdkClientError SDK_CLIENT_ERROR_UNSPECIFIED =
SdkClientError._(0, _omitEnumNames ? '' : 'SDK_CLIENT_ERROR_UNSPECIFIED');
static const SdkClientError SDK_CLIENT_ERROR_ALREADY_EXISTS =
SdkClientError._(
1, _omitEnumNames ? '' : 'SDK_CLIENT_ERROR_ALREADY_EXISTS');
static const SdkClientError SDK_CLIENT_ERROR_NOT_FOUND =
SdkClientError._(2, _omitEnumNames ? '' : 'SDK_CLIENT_ERROR_NOT_FOUND');
static const SdkClientError SDK_CLIENT_ERROR_HAS_RELATED_DATA =
SdkClientError._(
3, _omitEnumNames ? '' : 'SDK_CLIENT_ERROR_HAS_RELATED_DATA');
static const SdkClientError SDK_CLIENT_ERROR_INTERNAL =
SdkClientError._(4, _omitEnumNames ? '' : 'SDK_CLIENT_ERROR_INTERNAL');
static const $core.List<SdkClientError> values = <SdkClientError>[
SDK_CLIENT_ERROR_UNSPECIFIED,
SDK_CLIENT_ERROR_ALREADY_EXISTS,
SDK_CLIENT_ERROR_NOT_FOUND,
SDK_CLIENT_ERROR_HAS_RELATED_DATA,
SDK_CLIENT_ERROR_INTERNAL,
];
static final $core.List<SdkClientError?> _byValue =
$pb.ProtobufEnum.$_initByValueList(values, 4);
static SdkClientError? valueOf($core.int value) =>
value < 0 || value >= _byValue.length ? null : _byValue[value];
const SdkClientError._(super.value, super.name);
}
class AuthResult extends $pb.ProtobufEnum {
static const AuthResult AUTH_RESULT_UNSPECIFIED =
AuthResult._(0, _omitEnumNames ? '' : 'AUTH_RESULT_UNSPECIFIED');

View File

@@ -31,6 +31,25 @@ final $typed_data.Uint8List keyTypeDescriptor = $convert.base64Decode(
'CgdLZXlUeXBlEhgKFEtFWV9UWVBFX1VOU1BFQ0lGSUVEEAASFAoQS0VZX1RZUEVfRUQyNTUxOR'
'ABEhwKGEtFWV9UWVBFX0VDRFNBX1NFQ1AyNTZLMRACEhAKDEtFWV9UWVBFX1JTQRAD');
@$core.Deprecated('Use sdkClientErrorDescriptor instead')
const SdkClientError$json = {
'1': 'SdkClientError',
'2': [
{'1': 'SDK_CLIENT_ERROR_UNSPECIFIED', '2': 0},
{'1': 'SDK_CLIENT_ERROR_ALREADY_EXISTS', '2': 1},
{'1': 'SDK_CLIENT_ERROR_NOT_FOUND', '2': 2},
{'1': 'SDK_CLIENT_ERROR_HAS_RELATED_DATA', '2': 3},
{'1': 'SDK_CLIENT_ERROR_INTERNAL', '2': 4},
],
};
/// Descriptor for `SdkClientError`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List sdkClientErrorDescriptor = $convert.base64Decode(
'Cg5TZGtDbGllbnRFcnJvchIgChxTREtfQ0xJRU5UX0VSUk9SX1VOU1BFQ0lGSUVEEAASIwofU0'
'RLX0NMSUVOVF9FUlJPUl9BTFJFQURZX0VYSVNUUxABEh4KGlNES19DTElFTlRfRVJST1JfTk9U'
'X0ZPVU5EEAISJQohU0RLX0NMSUVOVF9FUlJPUl9IQVNfUkVMQVRFRF9EQVRBEAMSHQoZU0RLX0'
'NMSUVOVF9FUlJPUl9JTlRFUk5BTBAE');
@$core.Deprecated('Use authResultDescriptor instead')
const AuthResult$json = {
'1': 'AuthResult',
@@ -105,6 +124,131 @@ final $typed_data.Uint8List vaultStateDescriptor = $convert.base64Decode(
'VfVU5CT09UU1RSQVBQRUQQARIWChJWQVVMVF9TVEFURV9TRUFMRUQQAhIYChRWQVVMVF9TVEFU'
'RV9VTlNFQUxFRBADEhUKEVZBVUxUX1NUQVRFX0VSUk9SEAQ=');
@$core.Deprecated('Use sdkClientRevokeRequestDescriptor instead')
const SdkClientRevokeRequest$json = {
'1': 'SdkClientRevokeRequest',
'2': [
{'1': 'client_id', '3': 1, '4': 1, '5': 5, '10': 'clientId'},
],
};
/// Descriptor for `SdkClientRevokeRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sdkClientRevokeRequestDescriptor =
$convert.base64Decode(
'ChZTZGtDbGllbnRSZXZva2VSZXF1ZXN0EhsKCWNsaWVudF9pZBgBIAEoBVIIY2xpZW50SWQ=');
@$core.Deprecated('Use sdkClientEntryDescriptor instead')
const SdkClientEntry$json = {
'1': 'SdkClientEntry',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 5, '10': 'id'},
{'1': 'pubkey', '3': 2, '4': 1, '5': 12, '10': 'pubkey'},
{
'1': 'info',
'3': 3,
'4': 1,
'5': 11,
'6': '.arbiter.client.ClientInfo',
'10': 'info'
},
{'1': 'created_at', '3': 4, '4': 1, '5': 5, '10': 'createdAt'},
],
};
/// Descriptor for `SdkClientEntry`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sdkClientEntryDescriptor = $convert.base64Decode(
'Cg5TZGtDbGllbnRFbnRyeRIOCgJpZBgBIAEoBVICaWQSFgoGcHVia2V5GAIgASgMUgZwdWJrZX'
'kSLgoEaW5mbxgDIAEoCzIaLmFyYml0ZXIuY2xpZW50LkNsaWVudEluZm9SBGluZm8SHQoKY3Jl'
'YXRlZF9hdBgEIAEoBVIJY3JlYXRlZEF0');
@$core.Deprecated('Use sdkClientListDescriptor instead')
const SdkClientList$json = {
'1': 'SdkClientList',
'2': [
{
'1': 'clients',
'3': 1,
'4': 3,
'5': 11,
'6': '.arbiter.user_agent.SdkClientEntry',
'10': 'clients'
},
],
};
/// Descriptor for `SdkClientList`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sdkClientListDescriptor = $convert.base64Decode(
'Cg1TZGtDbGllbnRMaXN0EjwKB2NsaWVudHMYASADKAsyIi5hcmJpdGVyLnVzZXJfYWdlbnQuU2'
'RrQ2xpZW50RW50cnlSB2NsaWVudHM=');
@$core.Deprecated('Use sdkClientRevokeResponseDescriptor instead')
const SdkClientRevokeResponse$json = {
'1': 'SdkClientRevokeResponse',
'2': [
{
'1': 'ok',
'3': 1,
'4': 1,
'5': 11,
'6': '.google.protobuf.Empty',
'9': 0,
'10': 'ok'
},
{
'1': 'error',
'3': 2,
'4': 1,
'5': 14,
'6': '.arbiter.user_agent.SdkClientError',
'9': 0,
'10': 'error'
},
],
'8': [
{'1': 'result'},
],
};
/// Descriptor for `SdkClientRevokeResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sdkClientRevokeResponseDescriptor = $convert.base64Decode(
'ChdTZGtDbGllbnRSZXZva2VSZXNwb25zZRIoCgJvaxgBIAEoCzIWLmdvb2dsZS5wcm90b2J1Zi'
'5FbXB0eUgAUgJvaxI6CgVlcnJvchgCIAEoDjIiLmFyYml0ZXIudXNlcl9hZ2VudC5TZGtDbGll'
'bnRFcnJvckgAUgVlcnJvckIICgZyZXN1bHQ=');
@$core.Deprecated('Use sdkClientListResponseDescriptor instead')
const SdkClientListResponse$json = {
'1': 'SdkClientListResponse',
'2': [
{
'1': 'clients',
'3': 1,
'4': 1,
'5': 11,
'6': '.arbiter.user_agent.SdkClientList',
'9': 0,
'10': 'clients'
},
{
'1': 'error',
'3': 2,
'4': 1,
'5': 14,
'6': '.arbiter.user_agent.SdkClientError',
'9': 0,
'10': 'error'
},
],
'8': [
{'1': 'result'},
],
};
/// Descriptor for `SdkClientListResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sdkClientListResponseDescriptor = $convert.base64Decode(
'ChVTZGtDbGllbnRMaXN0UmVzcG9uc2USPQoHY2xpZW50cxgBIAEoCzIhLmFyYml0ZXIudXNlcl'
'9hZ2VudC5TZGtDbGllbnRMaXN0SABSB2NsaWVudHMSOgoFZXJyb3IYAiABKA4yIi5hcmJpdGVy'
'LnVzZXJfYWdlbnQuU2RrQ2xpZW50RXJyb3JIAFIFZXJyb3JCCAoGcmVzdWx0');
@$core.Deprecated('Use authChallengeRequestDescriptor instead')
const AuthChallengeRequest$json = {
'1': 'AuthChallengeRequest',
@@ -224,46 +368,61 @@ final $typed_data.Uint8List bootstrapEncryptedKeyDescriptor = $convert.base64Dec
'RleHQYAiABKAxSCmNpcGhlcnRleHQSJwoPYXNzb2NpYXRlZF9kYXRhGAMgASgMUg5hc3NvY2lh'
'dGVkRGF0YQ==');
@$core.Deprecated('Use clientConnectionRequestDescriptor instead')
const ClientConnectionRequest$json = {
'1': 'ClientConnectionRequest',
@$core.Deprecated('Use sdkClientConnectionRequestDescriptor instead')
const SdkClientConnectionRequest$json = {
'1': 'SdkClientConnectionRequest',
'2': [
{'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'},
{
'1': 'info',
'3': 2,
'4': 1,
'5': 11,
'6': '.arbiter.client.ClientInfo',
'10': 'info'
},
],
};
/// Descriptor for `SdkClientConnectionRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sdkClientConnectionRequestDescriptor =
$convert.base64Decode(
'ChpTZGtDbGllbnRDb25uZWN0aW9uUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleRIuCg'
'RpbmZvGAIgASgLMhouYXJiaXRlci5jbGllbnQuQ2xpZW50SW5mb1IEaW5mbw==');
@$core.Deprecated('Use sdkClientConnectionResponseDescriptor instead')
const SdkClientConnectionResponse$json = {
'1': 'SdkClientConnectionResponse',
'2': [
{'1': 'approved', '3': 1, '4': 1, '5': 8, '10': 'approved'},
{'1': 'pubkey', '3': 2, '4': 1, '5': 12, '10': 'pubkey'},
],
};
/// Descriptor for `SdkClientConnectionResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sdkClientConnectionResponseDescriptor =
$convert.base64Decode(
'ChtTZGtDbGllbnRDb25uZWN0aW9uUmVzcG9uc2USGgoIYXBwcm92ZWQYASABKAhSCGFwcHJvdm'
'VkEhYKBnB1YmtleRgCIAEoDFIGcHVia2V5');
@$core.Deprecated('Use sdkClientConnectionCancelDescriptor instead')
const SdkClientConnectionCancel$json = {
'1': 'SdkClientConnectionCancel',
'2': [
{'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'},
],
};
/// Descriptor for `ClientConnectionRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List clientConnectionRequestDescriptor =
/// Descriptor for `SdkClientConnectionCancel`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List sdkClientConnectionCancelDescriptor =
$convert.base64Decode(
'ChdDbGllbnRDb25uZWN0aW9uUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleQ==');
@$core.Deprecated('Use clientConnectionResponseDescriptor instead')
const ClientConnectionResponse$json = {
'1': 'ClientConnectionResponse',
'2': [
{'1': 'approved', '3': 1, '4': 1, '5': 8, '10': 'approved'},
],
};
/// Descriptor for `ClientConnectionResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List clientConnectionResponseDescriptor =
$convert.base64Decode(
'ChhDbGllbnRDb25uZWN0aW9uUmVzcG9uc2USGgoIYXBwcm92ZWQYASABKAhSCGFwcHJvdmVk');
@$core.Deprecated('Use clientConnectionCancelDescriptor instead')
const ClientConnectionCancel$json = {
'1': 'ClientConnectionCancel',
};
/// Descriptor for `ClientConnectionCancel`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List clientConnectionCancelDescriptor =
$convert.base64Decode('ChZDbGllbnRDb25uZWN0aW9uQ2FuY2Vs');
'ChlTZGtDbGllbnRDb25uZWN0aW9uQ2FuY2VsEhYKBnB1YmtleRgBIAEoDFIGcHVia2V5');
@$core.Deprecated('Use userAgentRequestDescriptor instead')
const UserAgentRequest$json = {
'1': 'UserAgentRequest',
'2': [
{'1': 'id', '3': 14, '4': 1, '5': 5, '10': 'id'},
{'1': 'id', '3': 16, '4': 1, '5': 5, '10': 'id'},
{
'1': 'auth_challenge_request',
'3': 1,
@@ -355,17 +514,35 @@ const UserAgentRequest$json = {
'10': 'evmGrantList'
},
{
'1': 'client_connection_response',
'1': 'sdk_client_connection_response',
'3': 11,
'4': 1,
'5': 11,
'6': '.arbiter.user_agent.ClientConnectionResponse',
'6': '.arbiter.user_agent.SdkClientConnectionResponse',
'9': 0,
'10': 'clientConnectionResponse'
'10': 'sdkClientConnectionResponse'
},
{
'1': 'sdk_client_revoke',
'3': 13,
'4': 1,
'5': 11,
'6': '.arbiter.user_agent.SdkClientRevokeRequest',
'9': 0,
'10': 'sdkClientRevoke'
},
{
'1': 'sdk_client_list',
'3': 14,
'4': 1,
'5': 11,
'6': '.google.protobuf.Empty',
'9': 0,
'10': 'sdkClientList'
},
{
'1': 'bootstrap_encrypted_key',
'3': 12,
'3': 15,
'4': 1,
'5': 11,
'6': '.arbiter.user_agent.BootstrapEncryptedKey',
@@ -380,7 +557,7 @@ const UserAgentRequest$json = {
/// Descriptor for `UserAgentRequest`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List userAgentRequestDescriptor = $convert.base64Decode(
'ChBVc2VyQWdlbnRSZXF1ZXN0Eg4KAmlkGA4gASgFUgJpZBJgChZhdXRoX2NoYWxsZW5nZV9yZX'
'ChBVc2VyQWdlbnRSZXF1ZXN0Eg4KAmlkGBAgASgFUgJpZBJgChZhdXRoX2NoYWxsZW5nZV9yZX'
'F1ZXN0GAEgASgLMiguYXJiaXRlci51c2VyX2FnZW50LkF1dGhDaGFsbGVuZ2VSZXF1ZXN0SABS'
'FGF1dGhDaGFsbGVuZ2VSZXF1ZXN0EmMKF2F1dGhfY2hhbGxlbmdlX3NvbHV0aW9uGAIgASgLMi'
'kuYXJiaXRlci51c2VyX2FnZW50LkF1dGhDaGFsbGVuZ2VTb2x1dGlvbkgAUhVhdXRoQ2hhbGxl'
@@ -395,17 +572,20 @@ final $typed_data.Uint8List userAgentRequestDescriptor = $convert.base64Decode(
'DmV2bUdyYW50Q3JlYXRlEk4KEGV2bV9ncmFudF9kZWxldGUYCSABKAsyIi5hcmJpdGVyLmV2bS'
'5Fdm1HcmFudERlbGV0ZVJlcXVlc3RIAFIOZXZtR3JhbnREZWxldGUSSAoOZXZtX2dyYW50X2xp'
'c3QYCiABKAsyIC5hcmJpdGVyLmV2bS5Fdm1HcmFudExpc3RSZXF1ZXN0SABSDGV2bUdyYW50TG'
'lzdBJsChpjbGllbnRfY29ubmVjdGlvbl9yZXNwb25zZRgLIAEoCzIsLmFyYml0ZXIudXNlcl9h'
'Z2VudC5DbGllbnRDb25uZWN0aW9uUmVzcG9uc2VIAFIYY2xpZW50Q29ubmVjdGlvblJlc3Bvbn'
'NlEmMKF2Jvb3RzdHJhcF9lbmNyeXB0ZWRfa2V5GAwgASgLMikuYXJiaXRlci51c2VyX2FnZW50'
'LkJvb3RzdHJhcEVuY3J5cHRlZEtleUgAUhVib290c3RyYXBFbmNyeXB0ZWRLZXlCCQoHcGF5bG'
'9hZA==');
'lzdBJ2Ch5zZGtfY2xpZW50X2Nvbm5lY3Rpb25fcmVzcG9uc2UYCyABKAsyLy5hcmJpdGVyLnVz'
'ZXJfYWdlbnQuU2RrQ2xpZW50Q29ubmVjdGlvblJlc3BvbnNlSABSG3Nka0NsaWVudENvbm5lY3'
'Rpb25SZXNwb25zZRJYChFzZGtfY2xpZW50X3Jldm9rZRgNIAEoCzIqLmFyYml0ZXIudXNlcl9h'
'Z2VudC5TZGtDbGllbnRSZXZva2VSZXF1ZXN0SABSD3Nka0NsaWVudFJldm9rZRJACg9zZGtfY2'
'xpZW50X2xpc3QYDiABKAsyFi5nb29nbGUucHJvdG9idWYuRW1wdHlIAFINc2RrQ2xpZW50TGlz'
'dBJjChdib290c3RyYXBfZW5jcnlwdGVkX2tleRgPIAEoCzIpLmFyYml0ZXIudXNlcl9hZ2VudC'
'5Cb290c3RyYXBFbmNyeXB0ZWRLZXlIAFIVYm9vdHN0cmFwRW5jcnlwdGVkS2V5QgkKB3BheWxv'
'YWQ=');
@$core.Deprecated('Use userAgentResponseDescriptor instead')
const UserAgentResponse$json = {
'1': 'UserAgentResponse',
'2': [
{'1': 'id', '3': 14, '4': 1, '5': 5, '9': 1, '10': 'id', '17': true},
{'1': 'id', '3': 16, '4': 1, '5': 5, '9': 1, '10': 'id', '17': true},
{
'1': 'auth_challenge',
'3': 1,
@@ -497,26 +677,44 @@ const UserAgentResponse$json = {
'10': 'evmGrantList'
},
{
'1': 'client_connection_request',
'1': 'sdk_client_connection_request',
'3': 11,
'4': 1,
'5': 11,
'6': '.arbiter.user_agent.ClientConnectionRequest',
'6': '.arbiter.user_agent.SdkClientConnectionRequest',
'9': 0,
'10': 'clientConnectionRequest'
'10': 'sdkClientConnectionRequest'
},
{
'1': 'client_connection_cancel',
'1': 'sdk_client_connection_cancel',
'3': 12,
'4': 1,
'5': 11,
'6': '.arbiter.user_agent.ClientConnectionCancel',
'6': '.arbiter.user_agent.SdkClientConnectionCancel',
'9': 0,
'10': 'clientConnectionCancel'
'10': 'sdkClientConnectionCancel'
},
{
'1': 'sdk_client_revoke_response',
'3': 13,
'4': 1,
'5': 11,
'6': '.arbiter.user_agent.SdkClientRevokeResponse',
'9': 0,
'10': 'sdkClientRevokeResponse'
},
{
'1': 'sdk_client_list_response',
'3': 14,
'4': 1,
'5': 11,
'6': '.arbiter.user_agent.SdkClientListResponse',
'9': 0,
'10': 'sdkClientListResponse'
},
{
'1': 'bootstrap_result',
'3': 13,
'3': 15,
'4': 1,
'5': 14,
'6': '.arbiter.user_agent.BootstrapResult',
@@ -532,7 +730,7 @@ const UserAgentResponse$json = {
/// Descriptor for `UserAgentResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List userAgentResponseDescriptor = $convert.base64Decode(
'ChFVc2VyQWdlbnRSZXNwb25zZRITCgJpZBgOIAEoBUgBUgJpZIgBARJKCg5hdXRoX2NoYWxsZW'
'ChFVc2VyQWdlbnRSZXNwb25zZRITCgJpZBgQIAEoBUgBUgJpZIgBARJKCg5hdXRoX2NoYWxsZW'
'5nZRgBIAEoCzIhLmFyYml0ZXIudXNlcl9hZ2VudC5BdXRoQ2hhbGxlbmdlSABSDWF1dGhDaGFs'
'bGVuZ2USQQoLYXV0aF9yZXN1bHQYAiABKA4yHi5hcmJpdGVyLnVzZXJfYWdlbnQuQXV0aFJlc3'
'VsdEgAUgphdXRoUmVzdWx0El0KFXVuc2VhbF9zdGFydF9yZXNwb25zZRgDIAEoCzInLmFyYml0'
@@ -546,10 +744,14 @@ final $typed_data.Uint8List userAgentResponseDescriptor = $convert.base64Decode(
'5ldm0uRXZtR3JhbnRDcmVhdGVSZXNwb25zZUgAUg5ldm1HcmFudENyZWF0ZRJPChBldm1fZ3Jh'
'bnRfZGVsZXRlGAkgASgLMiMuYXJiaXRlci5ldm0uRXZtR3JhbnREZWxldGVSZXNwb25zZUgAUg'
'5ldm1HcmFudERlbGV0ZRJJCg5ldm1fZ3JhbnRfbGlzdBgKIAEoCzIhLmFyYml0ZXIuZXZtLkV2'
'bUdyYW50TGlzdFJlc3BvbnNlSABSDGV2bUdyYW50TGlzdBJpChljbGllbnRfY29ubmVjdGlvbl'
'9yZXF1ZXN0GAsgASgLMisuYXJiaXRlci51c2VyX2FnZW50LkNsaWVudENvbm5lY3Rpb25SZXF1'
'ZXN0SABSF2NsaWVudENvbm5lY3Rpb25SZXF1ZXN0EmYKGGNsaWVudF9jb25uZWN0aW9uX2Nhbm'
'NlbBgMIAEoCzIqLmFyYml0ZXIudXNlcl9hZ2VudC5DbGllbnRDb25uZWN0aW9uQ2FuY2VsSABS'
'FmNsaWVudENvbm5lY3Rpb25DYW5jZWwSUAoQYm9vdHN0cmFwX3Jlc3VsdBgNIAEoDjIjLmFyYm'
'l0ZXIudXNlcl9hZ2VudC5Cb290c3RyYXBSZXN1bHRIAFIPYm9vdHN0cmFwUmVzdWx0QgkKB3Bh'
'eWxvYWRCBQoDX2lk');
'bUdyYW50TGlzdFJlc3BvbnNlSABSDGV2bUdyYW50TGlzdBJzCh1zZGtfY2xpZW50X2Nvbm5lY3'
'Rpb25fcmVxdWVzdBgLIAEoCzIuLmFyYml0ZXIudXNlcl9hZ2VudC5TZGtDbGllbnRDb25uZWN0'
'aW9uUmVxdWVzdEgAUhpzZGtDbGllbnRDb25uZWN0aW9uUmVxdWVzdBJwChxzZGtfY2xpZW50X2'
'Nvbm5lY3Rpb25fY2FuY2VsGAwgASgLMi0uYXJiaXRlci51c2VyX2FnZW50LlNka0NsaWVudENv'
'bm5lY3Rpb25DYW5jZWxIAFIZc2RrQ2xpZW50Q29ubmVjdGlvbkNhbmNlbBJqChpzZGtfY2xpZW'
'50X3Jldm9rZV9yZXNwb25zZRgNIAEoCzIrLmFyYml0ZXIudXNlcl9hZ2VudC5TZGtDbGllbnRS'
'ZXZva2VSZXNwb25zZUgAUhdzZGtDbGllbnRSZXZva2VSZXNwb25zZRJkChhzZGtfY2xpZW50X2'
'xpc3RfcmVzcG9uc2UYDiABKAsyKS5hcmJpdGVyLnVzZXJfYWdlbnQuU2RrQ2xpZW50TGlzdFJl'
'c3BvbnNlSABSFXNka0NsaWVudExpc3RSZXNwb25zZRJQChBib290c3RyYXBfcmVzdWx0GA8gAS'
'gOMiMuYXJiaXRlci51c2VyX2FnZW50LkJvb3RzdHJhcFJlc3VsdEgAUg9ib290c3RyYXBSZXN1'
'bHRCCQoHcGF5bG9hZEIFCgNfaWQ=');

View File

@@ -0,0 +1,34 @@
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:arbiter/providers/connection/connection_manager.dart';
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'list.g.dart';
@riverpod
Future<List<SdkClientEntry>?> sdkClients(Ref ref) async {
final connection = await ref.watch(connectionManagerProvider.future);
if (connection == null) {
return null;
}
final resp = await connection.request(
UserAgentRequest(sdkClientList: Empty()),
);
if (!resp.hasSdkClientListResponse()) {
throw Exception(
'Expected SDK client list response, got ${resp.whichPayload()}',
);
}
final result = resp.sdkClientListResponse;
switch (result.whichResult()) {
case SdkClientListResponse_Result.clients:
return result.clients.clients.toList(growable: false);
case SdkClientListResponse_Result.error:
throw Exception('Error listing SDK clients: ${result.error}');
case SdkClientListResponse_Result.notSet:
throw Exception('SDK client list response was empty.');
}
}

View File

@@ -0,0 +1,51 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'list.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(sdkClients)
final sdkClientsProvider = SdkClientsProvider._();
final class SdkClientsProvider
extends
$FunctionalProvider<
AsyncValue<List<SdkClientEntry>?>,
List<SdkClientEntry>?,
FutureOr<List<SdkClientEntry>?>
>
with
$FutureModifier<List<SdkClientEntry>?>,
$FutureProvider<List<SdkClientEntry>?> {
SdkClientsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'sdkClientsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$sdkClientsHash();
@$internal
@override
$FutureProviderElement<List<SdkClientEntry>?> $createElement(
$ProviderPointer pointer,
) => $FutureProviderElement(pointer);
@override
FutureOr<List<SdkClientEntry>?> create(Ref ref) {
return sdkClients(ref);
}
}
String _$sdkClientsHash() => r'833c249d9cc2f83921453e0ece354a9a2d9f4482';

View File

@@ -17,7 +17,7 @@ class Router extends RootStackRouter {
path: '/dashboard',
children: [
AutoRoute(page: EvmRoute.page, path: 'evm'),
AutoRoute(page: EvmGrantsRoute.page, path: 'grants'),
AutoRoute(page: ClientsRoute.page, path: 'clients'),
AutoRoute(page: AboutRoute.page, path: 'about'),
],
),

View File

@@ -10,11 +10,11 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:arbiter/screens/bootstrap.dart' as _i2;
import 'package:arbiter/screens/dashboard.dart' as _i4;
import 'package:arbiter/screens/dashboard.dart' as _i5;
import 'package:arbiter/screens/dashboard/about.dart' as _i1;
import 'package:arbiter/screens/dashboard/evm.dart' as _i6;
import 'package:arbiter/screens/dashboard/evm_grant_create.dart' as _i3;
import 'package:arbiter/screens/dashboard/evm_grants.dart' as _i5;
import 'package:arbiter/screens/dashboard/clients/table.dart' as _i3;
import 'package:arbiter/screens/dashboard/evm/evm.dart' as _i6;
import 'package:arbiter/screens/dashboard/evm/grants/grant_create.dart' as _i4;
import 'package:arbiter/screens/server_connection.dart' as _i7;
import 'package:arbiter/screens/server_info_setup.dart' as _i8;
import 'package:arbiter/screens/vault_setup.dart' as _i9;
@@ -54,7 +54,23 @@ class Bootstrap extends _i10.PageRouteInfo<void> {
}
/// generated route for
/// [_i3.CreateEvmGrantScreen]
/// [_i3.ClientsScreen]
class ClientsRoute extends _i10.PageRouteInfo<void> {
const ClientsRoute({List<_i10.PageRouteInfo>? children})
: super(ClientsRoute.name, initialChildren: children);
static const String name = 'ClientsRoute';
static _i10.PageInfo page = _i10.PageInfo(
name,
builder: (data) {
return const _i3.ClientsScreen();
},
);
}
/// generated route for
/// [_i4.CreateEvmGrantScreen]
class CreateEvmGrantRoute extends _i10.PageRouteInfo<void> {
const CreateEvmGrantRoute({List<_i10.PageRouteInfo>? children})
: super(CreateEvmGrantRoute.name, initialChildren: children);
@@ -64,13 +80,13 @@ class CreateEvmGrantRoute extends _i10.PageRouteInfo<void> {
static _i10.PageInfo page = _i10.PageInfo(
name,
builder: (data) {
return const _i3.CreateEvmGrantScreen();
return const _i4.CreateEvmGrantScreen();
},
);
}
/// generated route for
/// [_i4.DashboardRouter]
/// [_i5.DashboardRouter]
class DashboardRouter extends _i10.PageRouteInfo<void> {
const DashboardRouter({List<_i10.PageRouteInfo>? children})
: super(DashboardRouter.name, initialChildren: children);
@@ -80,23 +96,7 @@ class DashboardRouter extends _i10.PageRouteInfo<void> {
static _i10.PageInfo page = _i10.PageInfo(
name,
builder: (data) {
return const _i4.DashboardRouter();
},
);
}
/// generated route for
/// [_i5.EvmGrantsScreen]
class EvmGrantsRoute extends _i10.PageRouteInfo<void> {
const EvmGrantsRoute({List<_i10.PageRouteInfo>? children})
: super(EvmGrantsRoute.name, initialChildren: children);
static const String name = 'EvmGrantsRoute';
static _i10.PageInfo page = _i10.PageInfo(
name,
builder: (data) {
return const _i5.EvmGrantsScreen();
return const _i5.DashboardRouter();
},
);
}

View File

@@ -5,7 +5,7 @@ import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
const breakpoints = MaterialAdaptiveBreakpoints();
final routes = [const EvmRoute(), const EvmGrantsRoute(), const AboutRoute()];
final routes = [const EvmRoute(), const ClientsRoute(), const AboutRoute()];
@RoutePage()
class DashboardRouter extends StatelessWidget {
@@ -35,6 +35,11 @@ class DashboardRouter extends StatelessWidget {
selectedIcon: Icon(Icons.rule_folder),
label: "Grants",
),
NavigationDestination(
icon: Icon(Icons.devices_other_outlined),
selectedIcon: Icon(Icons.devices_other),
label: "Clients",
),
NavigationDestination(
icon: Icon(Icons.info_outline),
selectedIcon: Icon(Icons.info),

View File

@@ -0,0 +1,585 @@
import 'dart:math' as math;
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:arbiter/providers/connection/connection_manager.dart';
import 'package:arbiter/providers/sdk_clients/list.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:sizer/sizer.dart';
// ─── Palette ──────────────────────────────────────────────────────────────────
class _Palette {
static const ink = Color(0xFF15263C);
static const coral = Color(0xFFE26254);
static const cream = Color(0xFFFFFAF4);
static const line = Color(0x1A15263C);
}
// ─── Column width getters ─────────────────────────────────────────────────────
double get _accentStripWidth => 0.8.w;
double get _cellHPad => 1.8.w;
double get _colGap => 1.8.w;
double get _idColWidth => 8.w;
double get _nameColWidth => 20.w;
double get _versionColWidth => 12.w;
double get _registeredColWidth => 18.w;
double get _chevronColWidth => 4.w;
double get _tableMinWidth => 72.w;
// ─── Helpers ──────────────────────────────────────────────────────────────────
Color _accentColor(List<int> bytes) {
final seed = bytes.fold<int>(0, (v, b) => v + b);
final hue = (seed * 17) % 360;
return HSLColor.fromAHSL(1, hue.toDouble(), 0.68, 0.54).toColor();
}
// ed25519 public keys are always 32 bytes (64 hex chars); guard is defensive.
String _shortPubkey(List<int> bytes) {
final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
if (hex.length < 12) return '0x$hex';
return '0x${hex.substring(0, 8)}...${hex.substring(hex.length - 4)}';
}
String _fullPubkey(List<int> bytes) {
final hex = bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
return '0x$hex';
}
String _formatDate(int unixSecs) {
final dt = DateTime.fromMillisecondsSinceEpoch(unixSecs * 1000).toLocal();
return '${dt.year}-'
'${dt.month.toString().padLeft(2, '0')}-'
'${dt.day.toString().padLeft(2, '0')}';
}
String _formatError(Object error) {
final message = error.toString();
if (message.startsWith('Exception: ')) {
return message.substring('Exception: '.length);
}
return message;
}
// ─── State panel ─────────────────────────────────────────────────────────────
class _StatePanel extends StatelessWidget {
const _StatePanel({
required this.icon,
required this.title,
required this.body,
this.actionLabel,
this.onAction,
this.busy = false,
});
final IconData icon;
final String title;
final String body;
final String? actionLabel;
final Future<void> Function()? onAction;
final bool busy;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: _Palette.cream.withValues(alpha: 0.92),
border: Border.all(color: _Palette.line),
),
child: Padding(
padding: EdgeInsets.all(2.8.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (busy)
SizedBox(
width: 2.8.h,
height: 2.8.h,
child: const CircularProgressIndicator(strokeWidth: 2.5),
)
else
Icon(icon, size: 34, color: _Palette.coral),
SizedBox(height: 1.8.h),
Text(
title,
style: theme.textTheme.headlineSmall?.copyWith(
color: _Palette.ink,
fontWeight: FontWeight.w800,
),
),
SizedBox(height: 1.h),
Text(
body,
style: theme.textTheme.bodyLarge?.copyWith(
color: _Palette.ink.withValues(alpha: 0.72),
height: 1.5,
),
),
if (actionLabel != null && onAction != null) ...[
SizedBox(height: 2.h),
OutlinedButton.icon(
onPressed: () => onAction!(),
icon: const Icon(Icons.refresh),
label: Text(actionLabel!),
),
],
],
),
),
);
}
}
// ─── Header ───────────────────────────────────────────────────────────────────
class _Header extends StatelessWidget {
const _Header({required this.isBusy, required this.onRefresh});
final bool isBusy;
final Future<void> Function() onRefresh;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
padding: EdgeInsets.symmetric(horizontal: 1.6.w, vertical: 1.2.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18),
color: _Palette.cream,
border: Border.all(color: _Palette.line),
),
child: Row(
children: [
Expanded(
child: Text(
'SDK Clients',
style: theme.textTheme.titleMedium?.copyWith(
color: _Palette.ink,
fontWeight: FontWeight.w800,
),
),
),
if (isBusy) ...[
Text(
'Syncing',
style: theme.textTheme.bodySmall?.copyWith(
color: _Palette.ink.withValues(alpha: 0.62),
fontWeight: FontWeight.w700,
),
),
SizedBox(width: 1.w),
],
OutlinedButton.icon(
onPressed: () => onRefresh(),
style: OutlinedButton.styleFrom(
foregroundColor: _Palette.ink,
side: BorderSide(color: _Palette.line),
padding: EdgeInsets.symmetric(
horizontal: 1.4.w,
vertical: 1.2.h,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14),
),
),
icon: const Icon(Icons.refresh, size: 18),
label: const Text('Refresh'),
),
],
),
);
}
}
// ─── Table header row ─────────────────────────────────────────────────────────
class _ClientTableHeader extends StatelessWidget {
const _ClientTableHeader();
@override
Widget build(BuildContext context) {
final style = Theme.of(context).textTheme.labelLarge?.copyWith(
color: _Palette.ink.withValues(alpha: 0.72),
fontWeight: FontWeight.w800,
letterSpacing: 0.3,
);
return Container(
padding: EdgeInsets.symmetric(vertical: 1.4.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: _Palette.ink.withValues(alpha: 0.04),
),
child: Row(
children: [
SizedBox(width: _accentStripWidth + _cellHPad),
SizedBox(width: _idColWidth, child: Text('ID', style: style)),
SizedBox(width: _colGap),
SizedBox(width: _nameColWidth, child: Text('Name', style: style)),
SizedBox(width: _colGap),
SizedBox(
width: _versionColWidth,
child: Text('Version', style: style),
),
SizedBox(width: _colGap),
SizedBox(
width: _registeredColWidth,
child: Text('Registered', style: style),
),
SizedBox(width: _colGap),
SizedBox(width: _chevronColWidth),
SizedBox(width: _cellHPad),
],
),
);
}
}
// ─── Table row (owns its own expand state) ────────────────────────────────────
class _ClientTableRow extends HookWidget {
const _ClientTableRow({required this.client});
final SdkClientEntry client;
@override
Widget build(BuildContext context) {
final expanded = useState(false);
final accent = _accentColor(client.pubkey);
final theme = Theme.of(context);
final muted = _Palette.ink.withValues(alpha: 0.62);
final name = client.info.name.isEmpty ? '' : client.info.name;
final version = client.info.version.isEmpty ? '' : client.info.version;
final registered = _formatDate(client.createdAt);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ── Collapsed row ──────────────────────────────────────────────────────
GestureDetector(
onTap: () => expanded.value = !expanded.value,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18),
color: accent.withValues(alpha: 0.10),
border: Border.all(color: accent.withValues(alpha: 0.28)),
),
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
width: _accentStripWidth,
decoration: BoxDecoration(
color: accent,
borderRadius: const BorderRadius.horizontal(
left: Radius.circular(18),
),
),
),
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: _cellHPad,
vertical: 1.4.h,
),
child: Row(
children: [
SizedBox(
width: _idColWidth,
child: Text(
'${client.id}',
style: theme.textTheme.bodyLarge?.copyWith(
color: _Palette.ink,
),
),
),
SizedBox(width: _colGap),
SizedBox(
width: _nameColWidth,
child: Text(
name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyMedium?.copyWith(
color: _Palette.ink,
),
),
),
SizedBox(width: _colGap),
SizedBox(
width: _versionColWidth,
child: Text(
version,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(
color: muted,
),
),
),
SizedBox(width: _colGap),
SizedBox(
width: _registeredColWidth,
child: Text(
registered,
style: theme.textTheme.bodySmall?.copyWith(
color: muted,
),
),
),
SizedBox(width: _colGap),
SizedBox(
width: _chevronColWidth,
child: Icon(
expanded.value
? Icons.expand_more
: Icons.chevron_right,
color: muted,
),
),
],
),
),
),
],
),
),
),
),
// ── Expansion panel (AnimatedSize wraps only this section) ────────────
AnimatedSize(
duration: const Duration(milliseconds: 200),
curve: Curves.easeInOut,
child: expanded.value
? Container(
margin: EdgeInsets.only(top: 0.6.h),
padding: EdgeInsets.symmetric(
horizontal: 1.6.w,
vertical: 1.4.h,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
color: accent.withValues(alpha: 0.06),
border: Border(
left: BorderSide(color: accent, width: 0.4.w),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
client.info.description.isEmpty
? 'No description provided.'
: client.info.description,
style: theme.textTheme.bodyMedium?.copyWith(
color: muted,
height: 1.5,
),
),
SizedBox(height: 1.h),
Row(
children: [
Expanded(
child: Text(
_shortPubkey(client.pubkey),
style: theme.textTheme.bodySmall?.copyWith(
color: _Palette.ink,
fontFamily: 'monospace',
),
),
),
IconButton(
icon: const Icon(Icons.copy_rounded, size: 18),
color: muted,
onPressed: () async {
await Clipboard.setData(
ClipboardData(
text: _fullPubkey(client.pubkey),
),
);
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Public key copied.'),
behavior: SnackBarBehavior.floating,
),
);
},
),
],
),
],
),
)
: const SizedBox.shrink(),
),
],
);
}
}
// ─── Table container ──────────────────────────────────────────────────────────
class _ClientTable extends StatelessWidget {
const _ClientTable({required this.clients});
final List<SdkClientEntry> clients;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: _Palette.cream.withValues(alpha: 0.92),
border: Border.all(color: _Palette.line),
),
child: Padding(
padding: EdgeInsets.all(2.h),
child: LayoutBuilder(
builder: (context, constraints) {
final tableWidth = math.max(_tableMinWidth, constraints.maxWidth);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Registered clients',
style: theme.textTheme.titleLarge?.copyWith(
color: _Palette.ink,
fontWeight: FontWeight.w800,
),
),
SizedBox(height: 0.6.h),
Text(
'Every entry here has authenticated with Arbiter at least once.',
style: theme.textTheme.bodyMedium?.copyWith(
color: _Palette.ink.withValues(alpha: 0.70),
height: 1.4,
),
),
SizedBox(height: 1.6.h),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: tableWidth,
child: Column(
children: [
const _ClientTableHeader(),
SizedBox(height: 1.h),
for (var i = 0; i < clients.length; i++)
Padding(
padding: EdgeInsets.only(
bottom: i == clients.length - 1 ? 0 : 1.h,
),
child: _ClientTableRow(client: clients[i]),
),
],
),
),
),
],
);
},
),
),
);
}
}
// ─── Screen ───────────────────────────────────────────────────────────────────
@RoutePage()
class ClientsScreen extends HookConsumerWidget {
const ClientsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final clientsAsync = ref.watch(sdkClientsProvider);
final isConnected =
ref.watch(connectionManagerProvider).asData?.value != null;
void showMessage(String message) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message), behavior: SnackBarBehavior.floating),
);
}
Future<void> refresh() async {
try {
final future = ref.refresh(sdkClientsProvider.future);
await future;
} catch (error) {
showMessage(_formatError(error));
}
}
final clients = clientsAsync.asData?.value;
final content = switch (clientsAsync) {
AsyncLoading() when clients == null => const _StatePanel(
icon: Icons.hourglass_top,
title: 'Loading clients',
body: 'Pulling client registry from Arbiter.',
busy: true,
),
AsyncError(:final error) => _StatePanel(
icon: Icons.sync_problem,
title: 'Client registry unavailable',
body: _formatError(error),
actionLabel: 'Retry',
onAction: refresh,
),
_ when !isConnected => _StatePanel(
icon: Icons.portable_wifi_off,
title: 'No active server connection',
body: 'Reconnect to Arbiter to list SDK clients.',
actionLabel: 'Refresh',
onAction: refresh,
),
_ when clients != null && clients.isEmpty => _StatePanel(
icon: Icons.devices_other_outlined,
title: 'No clients yet',
body: 'SDK clients appear here once they register with Arbiter.',
actionLabel: 'Refresh',
onAction: refresh,
),
_ => _ClientTable(clients: clients ?? const []),
};
return Scaffold(
body: SafeArea(
child: RefreshIndicator.adaptive(
color: _Palette.ink,
backgroundColor: Colors.white,
onRefresh: refresh,
child: ListView(
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
padding: EdgeInsets.fromLTRB(2.4.w, 2.4.h, 2.4.w, 3.2.h),
children: [
_Header(isBusy: clientsAsync.isLoading, onRefresh: refresh),
SizedBox(height: 1.8.h),
content,
],
),
),
),
);
}
}

View File

@@ -2,7 +2,7 @@ import 'dart:math' as math;
import 'package:arbiter/proto/evm.pb.dart';
import 'package:arbiter/providers/connection/connection_manager.dart';
import 'package:arbiter/providers/evm.dart';
import 'package:arbiter/providers/evm/evm.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

View File

@@ -1,6 +1,6 @@
import 'package:arbiter/proto/evm.pb.dart';
import 'package:arbiter/providers/evm.dart';
import 'package:arbiter/providers/evm_grants.dart';
import 'package:arbiter/providers/evm/evm.dart';
import 'package:arbiter/providers/evm/evm_grants.dart';
import 'package:auto_route/auto_route.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart';

File diff suppressed because it is too large Load Diff