penumbra_sdk_custody/client.rs
1use anyhow::Result;
2use futures::FutureExt;
3use penumbra_sdk_proto::custody::v1::custody_service_client::CustodyServiceClient;
4use penumbra_sdk_proto::custody::v1::AuthorizeResponse;
5use std::{future::Future, pin::Pin};
6
7use tonic::codegen::Bytes;
8
9use crate::AuthorizeRequest;
10
11/// A well-typed wrapper around the GRPC custody protocol that uses Rust domain types rather than proto types.
12///
13/// The custody protocol is used by a wallet client to request authorization for
14/// a transaction they’ve constructed.
15///
16/// Modeling transaction authorization as an asynchronous RPC call encourages
17/// software to be written in a way that has a compatible data flow with a “soft
18/// HSM”, threshold signing, a hardware wallet, etc.
19///
20/// The custody protocol does not trust the client to authorize spends, so
21/// custody requests must contain sufficient information for the custodian to
22/// understand the transaction and determine whether or not it should be
23/// authorized.
24///
25/// This trait is a wrapper around the proto-generated [`CustodyServiceClient`] that serves two goals:
26///
27/// 1. It works on domain types rather than proto-generated types, avoiding conversions;
28/// 2. It's easier to write as a trait bound than the `CustodyServiceClient`,
29/// which requires complex bounds on its inner type to enforce that it is a
30/// tower `Service`
31pub trait CustodyClient {
32 /// Requests authorization of the transaction with the given description.
33 fn authorize(
34 &mut self,
35 request: AuthorizeRequest,
36 ) -> Pin<Box<dyn Future<Output = Result<AuthorizeResponse>> + Send + 'static>>;
37}
38
39impl<T> CustodyClient for CustodyServiceClient<T>
40where
41 T: tonic::client::GrpcService<tonic::body::BoxBody> + Send + Clone + 'static,
42 T::ResponseBody: tonic::codegen::Body<Data = Bytes> + Send + 'static,
43 T::Future: Send + 'static,
44 T::Error: Into<tonic::codegen::StdError>,
45 <T::ResponseBody as tonic::codegen::Body>::Error: Into<tonic::codegen::StdError> + Send,
46{
47 fn authorize(
48 &mut self,
49 request: AuthorizeRequest,
50 ) -> Pin<Box<dyn Future<Output = Result<AuthorizeResponse>> + Send + 'static>> {
51 let mut self2 = self.clone();
52 async move {
53 Ok(self2
54 .authorize(tonic::Request::new(request.into()))
55 .await?
56 .into_inner())
57 }
58 .boxed()
59 }
60}