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}