diff --git a/crates/proto/src/gen/penumbra.view.v1.rs b/crates/proto/src/gen/penumbra.view.v1.rs index 6ee1f98e77..d21151965b 100644 --- a/crates/proto/src/gen/penumbra.view.v1.rs +++ b/crates/proto/src/gen/penumbra.view.v1.rs @@ -1,5 +1,58 @@ #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct AuctionsRequest { + /// If present, filter balances to only include the account specified by the `AddressIndex`. + #[prost(message, optional, tag = "1")] + pub account_filter: ::core::option::Option< + super::super::core::keys::v1::AddressIndex, + >, + /// If present, include inactive auctions as well as active ones. + #[prost(bool, tag = "2")] + pub include_inactive: bool, + /// If set, query a fullnode for the current state of the auctions. + #[prost(bool, tag = "3")] + pub query_latest_state: bool, +} +impl ::prost::Name for AuctionsRequest { + const NAME: &'static str = "AuctionsRequest"; + 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 AuctionsResponse { + #[prost(message, optional, tag = "1")] + pub id: ::core::option::Option< + super::super::core::component::auction::v1alpha1::AuctionId, + >, + /// The note recording the auction NFT. + #[prost(message, optional, tag = "4")] + pub note_record: ::core::option::Option, + /// The state of the returned auction. + /// + /// Only present when `query_latest_state` was provided. + #[prost(message, optional, tag = "2")] + pub auction: ::core::option::Option<::pbjson_types::Any>, + /// The state of any DEX positions relevant to the returned auction. + /// + /// Only present when `query_latest_state` was provided. + /// Could be empty, depending on the auction state. + #[prost(message, repeated, tag = "3")] + pub positions: ::prost::alloc::vec::Vec< + super::super::core::component::dex::v1::Position, + >, +} +impl ::prost::Name for AuctionsResponse { + const NAME: &'static str = "AuctionsResponse"; + 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 AuthorizeAndBuildRequest { /// The transaction plan to authorize and build. #[prost(message, optional, tag = "1")] @@ -2457,6 +2510,32 @@ pub mod view_service_client { ); self.inner.server_streaming(req, path, codec).await } + /// Gets the auctions controlled by the user's wallet. + pub async fn auctions( + &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/Auctions", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("penumbra.view.v1.ViewService", "Auctions")); + self.inner.server_streaming(req, path, codec).await + } } } /// Generated server implementations. @@ -2805,6 +2884,17 @@ pub mod view_service_server { tonic::Response, tonic::Status, >; + /// Server streaming response type for the Auctions method. + type AuctionsStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + + Send + + 'static; + /// Gets the auctions controlled by the user's wallet. + async fn auctions( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; } /// The view RPC is used by a view client, who wants to do some /// transaction-related actions, to request data from a view service, which is @@ -4220,6 +4310,53 @@ pub mod view_service_server { }; Box::pin(fut) } + "/penumbra.view.v1.ViewService/Auctions" => { + #[allow(non_camel_case_types)] + struct AuctionsSvc(pub Arc); + impl< + T: ViewService, + > tonic::server::ServerStreamingService + for AuctionsSvc { + type Response = super::AuctionsResponse; + type ResponseStream = T::AuctionsStream; + 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 { + ::auctions(&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 = AuctionsSvc(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.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { Ok( diff --git a/crates/proto/src/gen/penumbra.view.v1.serde.rs b/crates/proto/src/gen/penumbra.view.v1.serde.rs index 7f4bce737c..1968cf511a 100644 --- a/crates/proto/src/gen/penumbra.view.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.view.v1.serde.rs @@ -847,6 +847,285 @@ impl<'de> serde::Deserialize<'de> for AssetsResponse { deserializer.deserialize_struct("penumbra.view.v1.AssetsResponse", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for AuctionsRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.account_filter.is_some() { + len += 1; + } + if self.include_inactive { + len += 1; + } + if self.query_latest_state { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.view.v1.AuctionsRequest", len)?; + if let Some(v) = self.account_filter.as_ref() { + struct_ser.serialize_field("accountFilter", v)?; + } + if self.include_inactive { + struct_ser.serialize_field("includeInactive", &self.include_inactive)?; + } + if self.query_latest_state { + struct_ser.serialize_field("queryLatestState", &self.query_latest_state)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for AuctionsRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "account_filter", + "accountFilter", + "include_inactive", + "includeInactive", + "query_latest_state", + "queryLatestState", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + AccountFilter, + IncludeInactive, + QueryLatestState, + __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 { + "accountFilter" | "account_filter" => Ok(GeneratedField::AccountFilter), + "includeInactive" | "include_inactive" => Ok(GeneratedField::IncludeInactive), + "queryLatestState" | "query_latest_state" => Ok(GeneratedField::QueryLatestState), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AuctionsRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.view.v1.AuctionsRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut account_filter__ = None; + let mut include_inactive__ = None; + let mut query_latest_state__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::AccountFilter => { + if account_filter__.is_some() { + return Err(serde::de::Error::duplicate_field("accountFilter")); + } + account_filter__ = map_.next_value()?; + } + GeneratedField::IncludeInactive => { + if include_inactive__.is_some() { + return Err(serde::de::Error::duplicate_field("includeInactive")); + } + include_inactive__ = Some(map_.next_value()?); + } + GeneratedField::QueryLatestState => { + if query_latest_state__.is_some() { + return Err(serde::de::Error::duplicate_field("queryLatestState")); + } + query_latest_state__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(AuctionsRequest { + account_filter: account_filter__, + include_inactive: include_inactive__.unwrap_or_default(), + query_latest_state: query_latest_state__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.view.v1.AuctionsRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for AuctionsResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.id.is_some() { + len += 1; + } + if self.note_record.is_some() { + len += 1; + } + if self.auction.is_some() { + len += 1; + } + if !self.positions.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.view.v1.AuctionsResponse", len)?; + if let Some(v) = self.id.as_ref() { + struct_ser.serialize_field("id", v)?; + } + if let Some(v) = self.note_record.as_ref() { + struct_ser.serialize_field("noteRecord", v)?; + } + if let Some(v) = self.auction.as_ref() { + struct_ser.serialize_field("auction", v)?; + } + if !self.positions.is_empty() { + struct_ser.serialize_field("positions", &self.positions)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for AuctionsResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "id", + "note_record", + "noteRecord", + "auction", + "positions", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Id, + NoteRecord, + Auction, + Positions, + __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 { + "id" => Ok(GeneratedField::Id), + "noteRecord" | "note_record" => Ok(GeneratedField::NoteRecord), + "auction" => Ok(GeneratedField::Auction), + "positions" => Ok(GeneratedField::Positions), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AuctionsResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.view.v1.AuctionsResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut id__ = None; + let mut note_record__ = None; + let mut auction__ = None; + let mut positions__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Id => { + if id__.is_some() { + return Err(serde::de::Error::duplicate_field("id")); + } + id__ = map_.next_value()?; + } + GeneratedField::NoteRecord => { + if note_record__.is_some() { + return Err(serde::de::Error::duplicate_field("noteRecord")); + } + note_record__ = map_.next_value()?; + } + GeneratedField::Auction => { + if auction__.is_some() { + return Err(serde::de::Error::duplicate_field("auction")); + } + auction__ = map_.next_value()?; + } + GeneratedField::Positions => { + if positions__.is_some() { + return Err(serde::de::Error::duplicate_field("positions")); + } + positions__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(AuctionsResponse { + id: id__, + note_record: note_record__, + auction: auction__, + positions: positions__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.view.v1.AuctionsResponse", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for AuthorizeAndBuildRequest { #[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 8abb21f0fe..1a1f858a53 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 668a318419..5f66711c15 100644 --- a/crates/view/src/service.rs +++ b/crates/view/src/service.rs @@ -387,6 +387,15 @@ impl ViewService for ViewServer { > + Send, >, >; + type AuctionsStream = + Pin> + Send>>; + + async fn auctions( + &self, + _request: tonic::Request, + ) -> Result, tonic::Status> { + unimplemented!("auctions") + } async fn broadcast_transaction( &self, diff --git a/proto/penumbra/penumbra/view/v1/view.proto b/proto/penumbra/penumbra/view/v1/view.proto index f3cf656701..7b24081c13 100644 --- a/proto/penumbra/penumbra/view/v1/view.proto +++ b/proto/penumbra/penumbra/view/v1/view.proto @@ -15,6 +15,8 @@ import "penumbra/core/num/v1/num.proto"; import "penumbra/core/transaction/v1/transaction.proto"; import "penumbra/core/txhash/v1/txhash.proto"; import "penumbra/crypto/tct/v1/tct.proto"; +import "penumbra/core/component/auction/v1alpha1/auction.proto"; +import "google/protobuf/any.proto"; // The view RPC is used by a view client, who wants to do some // transaction-related actions, to request data from a view service, which is @@ -140,6 +142,34 @@ service ViewService { // Get unbonding tokens for the given address index, optionally filtered by // whether the tokens are currently claimable. rpc UnbondingTokensByAddressIndex(UnbondingTokensByAddressIndexRequest) returns (stream UnbondingTokensByAddressIndexResponse); + + // Gets the auctions controlled by the user's wallet. + rpc Auctions(AuctionsRequest) returns (stream AuctionsResponse); +} + +message AuctionsRequest { + // If present, filter balances to only include the account specified by the `AddressIndex`. + core.keys.v1.AddressIndex account_filter = 1; + // If present, include inactive auctions as well as active ones. + bool include_inactive = 2; + // If set, query a fullnode for the current state of the auctions. + bool query_latest_state = 3; +} + +message AuctionsResponse { + core.component.auction.v1alpha1.AuctionId id = 1; + // The note recording the auction NFT. + SpendableNoteRecord note_record = 4; + + // The state of the returned auction. + // + // Only present when `query_latest_state` was provided. + google.protobuf.Any auction = 2; + // The state of any DEX positions relevant to the returned auction. + // + // Only present when `query_latest_state` was provided. + // Could be empty, depending on the auction state. + repeated core.component.dex.v1.Position positions = 3; } message AuthorizeAndBuildRequest {