diff --git a/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs b/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs index 383649ad97..6e23fc5c3f 100644 Binary files a/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs and b/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs differ diff --git a/crates/proto/src/gen/penumbra.view.v1.rs b/crates/proto/src/gen/penumbra.view.v1.rs index 9509181335..5ec54bf491 100644 --- a/crates/proto/src/gen/penumbra.view.v1.rs +++ b/crates/proto/src/gen/penumbra.view.v1.rs @@ -1,3 +1,32 @@ +/// There's only one transparent address per wallet, so this request has no parameters; +/// the message exists to satisfy forward-compatibility properties. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransparentAddressRequest {} +impl ::prost::Name for TransparentAddressRequest { + const NAME: &'static str = "TransparentAddressRequest"; + const PACKAGE: &'static str = "penumbra.view.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.view.v1.{}", Self::NAME) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransparentAddressResponse { + /// The raw (binary) transparent address + #[prost(message, optional, tag = "1")] + pub address: ::core::option::Option, + /// The t-address encoding of the transparent address + #[prost(string, tag = "2")] + pub encoding: ::prost::alloc::string::String, +} +impl ::prost::Name for TransparentAddressResponse { + const NAME: &'static str = "TransparentAddressResponse"; + const PACKAGE: &'static str = "penumbra.view.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!("penumbra.view.v1.{}", Self::NAME) + } +} /// Filters in an `AuctionsRequest` will be combined using `AND` logic -- that /// is, the more filters you add, the fewer responses you're likely to get. #[allow(clippy::derive_partial_eq_without_eq)] @@ -2117,6 +2146,34 @@ pub mod view_service_client { ); self.inner.unary(req, path, codec).await } + /// Returns the transparent address for the user's wallet. + pub async fn transparent_address( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/penumbra.view.v1.ViewService/TransparentAddress", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("penumbra.view.v1.ViewService", "TransparentAddress"), + ); + self.inner.unary(req, path, codec).await + } /// Query for wallet id pub async fn wallet_id( &mut self, @@ -2800,6 +2857,14 @@ pub mod view_service_server { tonic::Response, tonic::Status, >; + /// Returns the transparent address for the user's wallet. + async fn transparent_address( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Query for wallet id async fn wallet_id( &self, @@ -3596,6 +3661,53 @@ pub mod view_service_server { }; Box::pin(fut) } + "/penumbra.view.v1.ViewService/TransparentAddress" => { + #[allow(non_camel_case_types)] + struct TransparentAddressSvc(pub Arc); + impl< + T: ViewService, + > tonic::server::UnaryService + for TransparentAddressSvc { + type Response = super::TransparentAddressResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::transparent_address(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = TransparentAddressSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } "/penumbra.view.v1.ViewService/WalletId" => { #[allow(non_camel_case_types)] struct WalletIdSvc(pub Arc); diff --git a/crates/proto/src/gen/penumbra.view.v1.serde.rs b/crates/proto/src/gen/penumbra.view.v1.serde.rs index 140a24f431..d5b11ff8db 100644 --- a/crates/proto/src/gen/penumbra.view.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.view.v1.serde.rs @@ -8305,6 +8305,190 @@ impl<'de> serde::Deserialize<'de> for TransactionPlannerResponse { deserializer.deserialize_struct("penumbra.view.v1.TransactionPlannerResponse", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for TransparentAddressRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("penumbra.view.v1.TransparentAddressRequest", len)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TransparentAddressRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + Ok(GeneratedField::__SkipField__) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TransparentAddressRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.view.v1.TransparentAddressRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(TransparentAddressRequest { + }) + } + } + deserializer.deserialize_struct("penumbra.view.v1.TransparentAddressRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for TransparentAddressResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.address.is_some() { + len += 1; + } + if !self.encoding.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.view.v1.TransparentAddressResponse", len)?; + if let Some(v) = self.address.as_ref() { + struct_ser.serialize_field("address", v)?; + } + if !self.encoding.is_empty() { + struct_ser.serialize_field("encoding", &self.encoding)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for TransparentAddressResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "address", + "encoding", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Address, + Encoding, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "address" => Ok(GeneratedField::Address), + "encoding" => Ok(GeneratedField::Encoding), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = TransparentAddressResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.view.v1.TransparentAddressResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut address__ = None; + let mut encoding__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Address => { + if address__.is_some() { + return Err(serde::de::Error::duplicate_field("address")); + } + address__ = map_.next_value()?; + } + GeneratedField::Encoding => { + if encoding__.is_some() { + return Err(serde::de::Error::duplicate_field("encoding")); + } + encoding__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(TransparentAddressResponse { + address: address__, + encoding: encoding__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.view.v1.TransparentAddressResponse", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for UnbondingTokensByAddressIndexRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index 06cd62581e..4b21669be5 100644 Binary files a/crates/proto/src/gen/proto_descriptor.bin.no_lfs and b/crates/proto/src/gen/proto_descriptor.bin.no_lfs differ diff --git a/crates/view/src/service.rs b/crates/view/src/service.rs index 7971258eac..8e3c8ce26c 100644 --- a/crates/view/src/service.rs +++ b/crates/view/src/service.rs @@ -815,6 +815,25 @@ impl ViewService for ViewServer { address_index: fvk.address_index(&address).map(Into::into), })) } + async fn transparent_address( + &self, + _request: tonic::Request, + ) -> Result, tonic::Status> { + let fvk = + self.storage.full_viewing_key().await.map_err(|_| { + tonic::Status::failed_precondition("Error retrieving full viewing key") + })?; + + let encoding = fvk.incoming().transparent_address(); + let address: Address = encoding + .parse() + .map_err(|_| tonic::Status::internal("could not parse newly generated address"))?; + + Ok(tonic::Response::new(pb::TransparentAddressResponse { + address: Some(address.into()), + encoding, + })) + } #[instrument(skip_all, level = "trace")] async fn ephemeral_address( diff --git a/proto/penumbra/penumbra/view/v1/view.proto b/proto/penumbra/penumbra/view/v1/view.proto index 3a11c35f03..5f1ffb41b5 100644 --- a/proto/penumbra/penumbra/view/v1/view.proto +++ b/proto/penumbra/penumbra/view/v1/view.proto @@ -61,6 +61,9 @@ service ViewService { // Query for an address given an address index rpc AddressByIndex(AddressByIndexRequest) returns (AddressByIndexResponse); + // Returns the transparent address for the user's wallet. + rpc TransparentAddress(TransparentAddressRequest) returns (TransparentAddressResponse); + // Query for wallet id rpc WalletId(WalletIdRequest) returns (WalletIdResponse); @@ -148,6 +151,18 @@ service ViewService { rpc Auctions(AuctionsRequest) returns (stream AuctionsResponse); } +// There's only one transparent address per wallet, so this request has no parameters; +// the message exists to satisfy forward-compatibility properties. +message TransparentAddressRequest { +} + +message TransparentAddressResponse { + // The raw (binary) transparent address + core.keys.v1.Address address = 1; + // The t-address encoding of the transparent address + string encoding = 2; +} + // Filters in an `AuctionsRequest` will be combined using `AND` logic -- that // is, the more filters you add, the fewer responses you're likely to get. message AuctionsRequest {