diff --git a/protobufs/evm.proto b/protobufs/evm.proto
index 3ad3782..f20df52 100644
--- a/protobufs/evm.proto
+++ b/protobufs/evm.proto
@@ -12,7 +12,8 @@ enum EvmError {
}
message WalletEntry {
- bytes address = 1; // 20-byte Ethereum address
+ int32 id = 1;
+ bytes address = 2; // 20-byte Ethereum address
}
message WalletList {
diff --git a/protobufs/user_agent.proto b/protobufs/user_agent.proto
index ee3af0e..d2a697e 100644
--- a/protobufs/user_agent.proto
+++ b/protobufs/user_agent.proto
@@ -132,6 +132,19 @@ message SdkClientConnectionCancel {
bytes pubkey = 1;
}
+message SdkClientWalletAccess {
+ int32 client_id = 1;
+ int32 wallet_id = 2;
+}
+
+message SdkClientGrantWalletAccess {
+ repeated SdkClientWalletAccess accesses = 1;
+}
+
+message SdkClientRevokeWalletAccess {
+ repeated SdkClientWalletAccess accesses = 1;
+}
+
message UserAgentRequest {
int32 id = 16;
oneof payload {
@@ -146,9 +159,11 @@ message UserAgentRequest {
arbiter.evm.EvmGrantDeleteRequest evm_grant_delete = 9;
arbiter.evm.EvmGrantListRequest evm_grant_list = 10;
SdkClientConnectionResponse sdk_client_connection_response = 11;
- SdkClientRevokeRequest sdk_client_revoke = 13;
- google.protobuf.Empty sdk_client_list = 14;
- BootstrapEncryptedKey bootstrap_encrypted_key = 15;
+ SdkClientRevokeRequest sdk_client_revoke = 12;
+ google.protobuf.Empty sdk_client_list = 13;
+ BootstrapEncryptedKey bootstrap_encrypted_key = 14;
+ SdkClientGrantWalletAccess grant_wallet_access_list = 15;
+ SdkClientRevokeWalletAccess revoke_wallet_access_list = 17;
}
}
message UserAgentResponse {
diff --git a/server/crates/arbiter-server/src/actors/evm/mod.rs b/server/crates/arbiter-server/src/actors/evm/mod.rs
index 691e372..c875b18 100644
--- a/server/crates/arbiter-server/src/actors/evm/mod.rs
+++ b/server/crates/arbiter-server/src/actors/evm/mod.rs
@@ -105,7 +105,7 @@ impl EvmActor {
#[messages]
impl EvmActor {
#[message]
- pub async fn generate(&mut self) -> Result
{
+ pub async fn generate(&mut self) -> Result<(i32, Address), Error> {
let (mut key_cell, address) = safe_signer::generate(&mut self.rng);
let plaintext = key_cell.read_inline(|reader| SafeCell::new(reader.to_vec()));
@@ -117,15 +117,16 @@ impl EvmActor {
.map_err(|_| Error::KeyholderSend)?;
let mut conn = self.db.get().await?;
- insert_into(schema::evm_wallet::table)
+ let wallet_id = insert_into(schema::evm_wallet::table)
.values(&models::NewEvmWallet {
address: address.as_slice().to_vec(),
aead_encrypted_id: aead_id,
})
- .execute(&mut conn)
+ .returning(schema::evm_wallet::id)
+ .get_result(&mut conn)
.await?;
- Ok(address)
+ Ok((wallet_id, address))
}
#[message]
diff --git a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs
index 397b563..85074a1 100644
--- a/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs
+++ b/server/crates/arbiter-server/src/actors/user_agent/session/connection.rs
@@ -276,7 +276,7 @@ impl UserAgentSession {
#[messages]
impl UserAgentSession {
#[message]
- pub(crate) async fn handle_evm_wallet_create(&mut self) -> Result {
+ pub(crate) async fn handle_evm_wallet_create(&mut self) -> Result<(i32, Address), Error> {
match self.props.actors.evm.ask(Generate {}).await {
Ok(address) => Ok(address),
Err(SendError::HandlerError(err)) => Err(Error::internal(format!(
diff --git a/server/crates/arbiter-server/src/grpc/client.rs b/server/crates/arbiter-server/src/grpc/client.rs
index 063a5b2..cd032f4 100644
--- a/server/crates/arbiter-server/src/grpc/client.rs
+++ b/server/crates/arbiter-server/src/grpc/client.rs
@@ -22,10 +22,11 @@ use crate::{
keyholder::KeyHolderState,
},
grpc::request_tracker::RequestTracker,
- utils::defer,
};
mod auth;
+mod inbound;
+mod outbound;
async fn dispatch_loop(
mut bi: GrpcBi,
@@ -33,52 +34,53 @@ async fn dispatch_loop(
mut request_tracker: RequestTracker,
) {
loop {
- let Some(conn) = bi.recv().await else {
+ let Some(message) = bi.recv().await else { return };
+
+ let conn = match message {
+ Ok(conn) => conn,
+ Err(err) => {
+ warn!(error = ?err, "Failed to receive client request");
+ return;
+ }
+ };
+
+ let request_id = match request_tracker.request(conn.request_id) {
+ Ok(id) => id,
+ Err(err) => {
+ let _ = bi.send(Err(err)).await;
+ return;
+ }
+ };
+
+ let Some(payload) = conn.payload else {
+ let _ = bi.send(Err(Status::invalid_argument("Missing client request payload"))).await;
return;
};
- if dispatch_conn_message(&mut bi, &actor, &mut request_tracker, conn)
- .await
- .is_err()
- {
- return;
+ match dispatch_inner(&actor, payload).await {
+ Ok(response) => {
+ if bi.send(Ok(ClientResponse {
+ request_id: Some(request_id),
+ payload: Some(response),
+ })).await.is_err() {
+ return;
+ }
+ }
+ Err(status) => {
+ let _ = bi.send(Err(status)).await;
+ return;
+ }
}
}
}
-async fn dispatch_conn_message(
- bi: &mut GrpcBi,
+async fn dispatch_inner(
actor: &ActorRef,
- request_tracker: &mut RequestTracker,
- conn: Result,
-) -> Result<(), ()> {
- let conn = match conn {
- Ok(conn) => conn,
- Err(err) => {
- warn!(error = ?err, "Failed to receive client request");
- return Err(());
- }
- };
-
- let request_id = match request_tracker.request(conn.request_id) {
- Ok(request_id) => request_id,
- Err(err) => {
- let _ = bi.send(Err(err)).await;
- return Err(());
- }
- };
- let Some(payload) = conn.payload else {
- let _ = bi
- .send(Err(Status::invalid_argument(
- "Missing client request payload",
- )))
- .await;
- return Err(());
- };
-
- let payload = match payload {
- ClientRequestPayload::QueryVaultState(_) => ClientResponsePayload::VaultState(
- match actor.ask(HandleQueryVaultState {}).await {
+ payload: ClientRequestPayload,
+) -> Result {
+ match payload {
+ ClientRequestPayload::QueryVaultState(_) => {
+ let state = match actor.ask(HandleQueryVaultState {}).await {
Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed,
Ok(KeyHolderState::Unsealed) => ProtoVaultState::Unsealed,
@@ -87,46 +89,30 @@ async fn dispatch_conn_message(
warn!(error = ?err, "Failed to query vault state");
ProtoVaultState::Error
}
- }
- .into(),
- ),
+ };
+ Ok(ClientResponsePayload::VaultState(state.into()))
+ }
payload => {
warn!(?payload, "Unsupported post-auth client request");
- let _ = bi
- .send(Err(Status::invalid_argument("Unsupported client request")))
- .await;
- return Err(());
- }
- };
-
- bi.send(Ok(ClientResponse {
- request_id: Some(request_id),
- payload: Some(payload),
- }))
- .await
- .map_err(|_| ())
-}
-
-pub async fn start(conn: ClientConnection, mut bi: GrpcBi) {
- let mut conn = conn;
- let mut request_tracker = RequestTracker::default();
-
- match auth::start(&mut conn, &mut bi, &mut request_tracker).await {
- Ok(_) => {
- let actor =
- client::session::ClientSession::spawn(client::session::ClientSession::new(conn));
- let actor_for_cleanup = actor.clone();
- let _ = defer(move || {
- actor_for_cleanup.kill();
- });
-
- info!("Client authenticated successfully");
- dispatch_loop(bi, actor, request_tracker).await;
- }
- Err(e) => {
- let mut transport = auth::AuthTransportAdapter::new(&mut bi, &mut request_tracker);
- let _ = transport.send(Err(e.clone())).await;
- warn!(error = ?e, "Authentication failed");
+ Err(Status::invalid_argument("Unsupported client request"))
}
}
}
+
+pub async fn start(mut conn: ClientConnection, mut bi: GrpcBi) {
+ let mut request_tracker = RequestTracker::default();
+
+ if let Err(e) = auth::start(&mut conn, &mut bi, &mut request_tracker).await {
+ let mut transport = auth::AuthTransportAdapter::new(&mut bi, &mut request_tracker);
+ let _ = transport.send(Err(e.clone())).await;
+ warn!(error = ?e, "Client authentication failed");
+ return;
+ };
+
+ let actor = client::session::ClientSession::spawn(client::session::ClientSession::new(conn));
+ let actor_for_cleanup = actor.clone();
+
+ info!("Client authenticated successfully");
+ dispatch_loop(bi, actor, request_tracker).await;
+ actor_for_cleanup.kill();
+}
diff --git a/server/crates/arbiter-server/src/grpc/client/inbound.rs b/server/crates/arbiter-server/src/grpc/client/inbound.rs
new file mode 100644
index 0000000..e69de29
diff --git a/server/crates/arbiter-server/src/grpc/client/outbound.rs b/server/crates/arbiter-server/src/grpc/client/outbound.rs
new file mode 100644
index 0000000..e69de29
diff --git a/server/crates/arbiter-server/src/grpc/mod.rs b/server/crates/arbiter-server/src/grpc/mod.rs
index de60b84..149f0cb 100644
--- a/server/crates/arbiter-server/src/grpc/mod.rs
+++ b/server/crates/arbiter-server/src/grpc/mod.rs
@@ -18,6 +18,19 @@ pub mod client;
mod request_tracker;
pub mod user_agent;
+pub trait Convert {
+ type Output;
+
+ fn convert(self) -> Self::Output;
+}
+
+pub trait TryConvert {
+ type Output;
+ type Error;
+
+ fn try_convert(self) -> Result;
+}
+
#[async_trait]
impl arbiter_proto::proto::arbiter_service_server::ArbiterService for super::Server {
type UserAgentStream = ReceiverStream>;
diff --git a/server/crates/arbiter-server/src/grpc/user_agent.rs b/server/crates/arbiter-server/src/grpc/user_agent.rs
index 2742660..470b479 100644
--- a/server/crates/arbiter-server/src/grpc/user_agent.rs
+++ b/server/crates/arbiter-server/src/grpc/user_agent.rs
@@ -4,26 +4,21 @@ use arbiter_proto::{
proto::{
client::ClientInfo as ProtoClientMetadata,
evm::{
- EtherTransferSettings as ProtoEtherTransferSettings, EvmError as ProtoEvmError,
- EvmGrantCreateRequest, EvmGrantCreateResponse, EvmGrantDeleteRequest,
- EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse, GrantEntry,
- SharedSettings as ProtoSharedSettings, SpecificGrant as ProtoSpecificGrant,
- TokenTransferSettings as ProtoTokenTransferSettings,
- TransactionRateLimit as ProtoTransactionRateLimit,
- VolumeRateLimit as ProtoVolumeRateLimit, WalletCreateResponse, WalletEntry, WalletList,
- WalletListResponse, evm_grant_create_response::Result as EvmGrantCreateResult,
+ EvmError as ProtoEvmError, EvmGrantCreateRequest, EvmGrantCreateResponse,
+ EvmGrantDeleteRequest, EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse,
+ GrantEntry, WalletCreateResponse, WalletEntry, WalletList, WalletListResponse,
+ evm_grant_create_response::Result as EvmGrantCreateResult,
evm_grant_delete_response::Result as EvmGrantDeleteResult,
evm_grant_list_response::Result as EvmGrantListResult,
- specific_grant::Grant as ProtoSpecificGrantType,
wallet_create_response::Result as WalletCreateResult,
wallet_list_response::Result as WalletListResult,
},
user_agent::{
BootstrapEncryptedKey as ProtoBootstrapEncryptedKey,
BootstrapResult as ProtoBootstrapResult,
- SdkClientEntry as ProtoSdkClientEntry, SdkClientError as ProtoSdkClientError,
SdkClientConnectionCancel as ProtoSdkClientConnectionCancel,
SdkClientConnectionRequest as ProtoSdkClientConnectionRequest,
+ SdkClientEntry as ProtoSdkClientEntry, SdkClientError as ProtoSdkClientError,
SdkClientList as ProtoSdkClientList,
SdkClientListResponse as ProtoSdkClientListResponse,
UnsealEncryptedKey as ProtoUnsealEncryptedKey, UnsealResult as ProtoUnsealResult,
@@ -35,9 +30,7 @@ use arbiter_proto::{
},
transport::{Error as TransportError, Receiver, Sender, grpc::GrpcBi},
};
-use prost_types::{Timestamp as ProtoTimestamp, };
use async_trait::async_trait;
-use chrono::{TimeZone, Utc};
use kameo::{
actor::{ActorRef, Spawn as _},
error::SendError,
@@ -51,23 +44,18 @@ use crate::{
user_agent::{
OutOfBand, UserAgentConnection, UserAgentSession,
session::{
- BootstrapError, Error, HandleBootstrapEncryptedKey, HandleEvmWalletCreate,
+ BootstrapError, HandleBootstrapEncryptedKey, HandleEvmWalletCreate,
HandleEvmWalletList, HandleGrantCreate, HandleGrantDelete, HandleGrantList,
HandleNewClientApprove, HandleQueryVaultState, HandleSdkClientList,
- HandleUnsealEncryptedKey,
- HandleUnsealRequest, UnsealError,
+ HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError,
},
},
},
- evm::policies::{
- Grant, SharedGrantSettings, SpecificGrant, TransactionRateLimit, VolumeRateLimit,
- ether_transfer, token_transfers,
- },
- grpc::request_tracker::RequestTracker,
- utils::defer,
+ grpc::{Convert, TryConvert, request_tracker::RequestTracker},
};
-use alloy::primitives::{Address, U256};
mod auth;
+mod inbound;
+mod outbound;
pub struct OutOfBandAdapter(mpsc::Sender);
@@ -95,92 +83,105 @@ async fn dispatch_loop(
return;
};
- if send_out_of_band(&mut bi, oob).await.is_err() {
+ let payload = match oob {
+ OutOfBand::ClientConnectionRequest { profile } => {
+ UserAgentResponsePayload::SdkClientConnectionRequest(ProtoSdkClientConnectionRequest {
+ pubkey: profile.pubkey.to_bytes().to_vec(),
+ info: Some(ProtoClientMetadata {
+ name: profile.metadata.name,
+ description: profile.metadata.description,
+ version: profile.metadata.version,
+ }),
+ })
+ }
+ OutOfBand::ClientConnectionCancel { pubkey } => {
+ UserAgentResponsePayload::SdkClientConnectionCancel(ProtoSdkClientConnectionCancel {
+ pubkey: pubkey.to_bytes().to_vec(),
+ })
+ }
+ };
+
+ if bi.send(Ok(UserAgentResponse { id: None, payload: Some(payload) })).await.is_err() {
return;
}
}
- conn = bi.recv() => {
- let Some(conn) = conn else {
+ message = bi.recv() => {
+ let Some(message) = message else { return; };
+
+ let conn = match message {
+ Ok(conn) => conn,
+ Err(err) => {
+ warn!(error = ?err, "Failed to receive user agent request");
+ return;
+ }
+ };
+
+ let request_id = match request_tracker.request(conn.id) {
+ Ok(id) => id,
+ Err(err) => {
+ let _ = bi.send(Err(err)).await;
+ return;
+ }
+ };
+
+ let Some(payload) = conn.payload else {
+ let _ = bi.send(Err(Status::invalid_argument("Missing user-agent request payload"))).await;
return;
};
- if let Err(e) = dispatch_conn_message(&mut bi, &actor, &mut request_tracker, conn)
- .await
-
- {
- error!(error = ?e, "Error handling user agent message");
- return;
+ match dispatch_inner(&actor, payload).await {
+ Ok(Some(response)) => {
+ if bi.send(Ok(UserAgentResponse {
+ id: Some(request_id),
+ payload: Some(response),
+ })).await.is_err() {
+ return;
+ }
+ }
+ Ok(None) => {}
+ Err(status) => {
+ error!(?status, "Failed to process user agent request");
+ let _ = bi.send(Err(status)).await;
+ return;
+ }
}
}
}
}
}
-async fn dispatch_conn_message(
- bi: &mut GrpcBi,
+async fn dispatch_inner(
actor: &ActorRef,
- request_tracker: &mut RequestTracker,
- conn: Result,
-) -> Result<(), ()> {
- let conn = match conn {
- Ok(conn) => conn,
- Err(err) => {
- warn!(error = ?err, "Failed to receive user agent request");
- return Err(());
- }
- };
-
- let request_id = match request_tracker.request(conn.id) {
- Ok(request_id) => request_id,
- Err(err) => {
- let _ = bi.send(Err(err)).await;
- return Err(());
- }
- };
-
- let Some(payload) = conn.payload else {
- let _ = bi
- .send(Err(Status::invalid_argument(
- "Missing user-agent request payload",
- )))
- .await;
- return Err(());
- };
-
- let payload = match payload {
+ payload: UserAgentRequestPayload,
+) -> Result