sui_rpc_api/client/
mod.rs1use tap::Pipe;
5use tonic::metadata::MetadataMap;
6
7use prost_types::FieldMask;
8use sui_rpc::field::FieldMaskUtil;
9use sui_rpc::proto::sui::rpc::v2 as proto;
10use sui_rpc::proto::TryFromProtoError;
11use sui_types::base_types::{ObjectID, SequenceNumber};
12use sui_types::effects::{TransactionEffects, TransactionEvents};
13use sui_types::full_checkpoint_content::CheckpointData;
14use sui_types::full_checkpoint_content::ObjectSet;
15use sui_types::messages_checkpoint::{CertifiedCheckpointSummary, CheckpointSequenceNumber};
16use sui_types::object::Object;
17use sui_types::transaction::Transaction;
18
19pub use sui_rpc::client::AuthInterceptor;
20pub use sui_rpc::client::ResponseExt;
21
22pub type Result<T, E = tonic::Status> = std::result::Result<T, E>;
23pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
24
25use tonic::Status;
26
27#[derive(Clone)]
28pub struct Client(sui_rpc::client::v2::Client);
29
30impl Client {
31 pub fn new<T>(uri: T) -> Result<Self>
32 where
33 T: TryInto<http::Uri>,
34 T::Error: Into<BoxError>,
35 {
36 sui_rpc::client::v2::Client::new(uri).map(Self)
37 }
38
39 pub fn with_auth(self, auth: AuthInterceptor) -> Self {
40 Self(self.0.with_auth(auth))
41 }
42
43 pub async fn get_latest_checkpoint(&mut self) -> Result<CertifiedCheckpointSummary> {
44 self.get_checkpoint_internal(None).await
45 }
46
47 pub async fn get_checkpoint_summary(
48 &mut self,
49 sequence_number: CheckpointSequenceNumber,
50 ) -> Result<CertifiedCheckpointSummary> {
51 self.get_checkpoint_internal(Some(sequence_number)).await
52 }
53
54 async fn get_checkpoint_internal(
55 &mut self,
56 sequence_number: Option<CheckpointSequenceNumber>,
57 ) -> Result<CertifiedCheckpointSummary> {
58 let mut request = proto::GetCheckpointRequest::default()
59 .with_read_mask(FieldMask::from_paths(["summary.bcs", "signature"]));
60 request.checkpoint_id = sequence_number.map(|sequence_number| {
61 proto::get_checkpoint_request::CheckpointId::SequenceNumber(sequence_number)
62 });
63
64 let (metadata, checkpoint, _extentions) = self
65 .0
66 .ledger_client()
67 .get_checkpoint(request)
68 .await?
69 .into_parts();
70
71 let checkpoint = checkpoint
72 .checkpoint
73 .ok_or_else(|| tonic::Status::not_found("no checkpoint returned"))?;
74 certified_checkpoint_summary_try_from_proto(&checkpoint)
75 .map_err(|e| status_from_error_with_metadata(e, metadata))
76 }
77
78 pub async fn get_full_checkpoint(
79 &mut self,
80 sequence_number: CheckpointSequenceNumber,
81 ) -> Result<CheckpointData> {
82 let request = proto::GetCheckpointRequest::by_sequence_number(sequence_number)
83 .with_read_mask(FieldMask::from_paths([
84 "summary.bcs",
85 "signature",
86 "contents.bcs",
87 "transactions.transaction.bcs",
88 "transactions.effects.bcs",
89 "transactions.effects.unchanged_loaded_runtime_objects",
90 "transactions.events.bcs",
91 "objects.objects.bcs",
92 ]));
93
94 let (metadata, response, _extentions) = self
95 .0
96 .ledger_client()
97 .max_decoding_message_size(128 * 1024 * 1024)
98 .get_checkpoint(request)
99 .await?
100 .into_parts();
101
102 let checkpoint = response
103 .checkpoint
104 .ok_or_else(|| tonic::Status::not_found("no checkpoint returned"))?;
105 sui_types::full_checkpoint_content::Checkpoint::try_from(&checkpoint)
106 .map(Into::into)
107 .map_err(|e| status_from_error_with_metadata(e, metadata))
108 }
109
110 pub async fn get_object(&mut self, object_id: ObjectID) -> Result<Object> {
111 self.get_object_internal(object_id, None).await
112 }
113
114 pub async fn get_object_with_version(
115 &mut self,
116 object_id: ObjectID,
117 version: SequenceNumber,
118 ) -> Result<Object> {
119 self.get_object_internal(object_id, Some(version.value()))
120 .await
121 }
122
123 async fn get_object_internal(
124 &mut self,
125 object_id: ObjectID,
126 version: Option<u64>,
127 ) -> Result<Object> {
128 let mut request = proto::GetObjectRequest::new(&object_id.into())
129 .with_read_mask(FieldMask::from_paths(["bcs"]));
130 request.version = version;
131
132 let (metadata, object, _extentions) = self
133 .0
134 .ledger_client()
135 .get_object(request)
136 .await?
137 .into_parts();
138
139 let object = object
140 .object
141 .ok_or_else(|| tonic::Status::not_found("no object returned"))?;
142 object_try_from_proto(&object).map_err(|e| status_from_error_with_metadata(e, metadata))
143 }
144
145 pub async fn execute_transaction(
146 &mut self,
147 transaction: &Transaction,
148 ) -> Result<TransactionExecutionResponse> {
149 let signatures = transaction
150 .inner()
151 .tx_signatures
152 .iter()
153 .map(|signature| {
154 let mut message = proto::UserSignature::default();
155 message.bcs = Some(signature.as_ref().to_vec().into());
156 message
157 })
158 .collect();
159
160 let request = proto::ExecuteTransactionRequest::new({
161 let mut tx = proto::Transaction::default();
162 tx.bcs = Some(
163 proto::Bcs::serialize(&transaction.inner().intent_message.value)
164 .map_err(|e| Status::from_error(e.into()))?,
165 );
166 tx
167 })
168 .with_signatures(signatures)
169 .with_read_mask(FieldMask::from_paths([
170 "effects.bcs",
171 "events.bcs",
172 "balance_changes",
173 "objects.objects.bcs",
174 ]));
175
176 let (metadata, response, _extentions) = self
177 .0
178 .execution_client()
179 .execute_transaction(request)
180 .await?
181 .into_parts();
182
183 execute_transaction_response_try_from_proto(&response)
184 .map_err(|e| status_from_error_with_metadata(e, metadata))
185 }
186}
187
188#[derive(Debug)]
189pub struct TransactionExecutionResponse {
190 pub effects: TransactionEffects,
191 pub events: Option<TransactionEvents>,
192 pub balance_changes: Vec<sui_sdk_types::BalanceChange>,
193 pub objects: ObjectSet,
194}
195
196#[allow(clippy::result_large_err)]
198fn certified_checkpoint_summary_try_from_proto(
199 checkpoint: &proto::Checkpoint,
200) -> Result<CertifiedCheckpointSummary, TryFromProtoError> {
201 let summary = checkpoint
202 .summary
203 .as_ref()
204 .and_then(|summary| summary.bcs.as_ref())
205 .ok_or_else(|| TryFromProtoError::missing("summary.bcs"))?
206 .deserialize()
207 .map_err(|e| TryFromProtoError::invalid("summary.bcs", e))?;
208
209 let signature = sui_types::crypto::AuthorityStrongQuorumSignInfo::from(
210 sui_sdk_types::ValidatorAggregatedSignature::try_from(
211 checkpoint
212 .signature
213 .as_ref()
214 .ok_or_else(|| TryFromProtoError::missing("signature"))?,
215 )
216 .map_err(|e| TryFromProtoError::invalid("signature", e))?,
217 );
218
219 Ok(CertifiedCheckpointSummary::new_from_data_and_sig(
220 summary, signature,
221 ))
222}
223
224#[allow(clippy::result_large_err)]
226fn object_try_from_proto(object: &proto::Object) -> Result<Object, TryFromProtoError> {
227 object
228 .bcs
229 .as_ref()
230 .ok_or_else(|| TryFromProtoError::missing("bcs"))?
231 .deserialize()
232 .map_err(|e| TryFromProtoError::invalid("bcs", e))
233}
234
235#[allow(clippy::result_large_err)]
237fn execute_transaction_response_try_from_proto(
238 response: &proto::ExecuteTransactionResponse,
239) -> Result<TransactionExecutionResponse, TryFromProtoError> {
240 let executed_transaction = response
241 .transaction
242 .as_ref()
243 .ok_or_else(|| TryFromProtoError::missing("transaction"))?;
244
245 let effects = executed_transaction
246 .effects
247 .as_ref()
248 .and_then(|effects| effects.bcs.as_ref())
249 .ok_or_else(|| TryFromProtoError::missing("effects_bcs"))?
250 .deserialize()
251 .map_err(|e| TryFromProtoError::invalid("effects.bcs", e))?;
252 let events = executed_transaction
253 .events
254 .as_ref()
255 .and_then(|events| events.bcs.as_ref())
256 .map(|bcs| bcs.deserialize())
257 .transpose()
258 .map_err(|e| TryFromProtoError::invalid("events.bcs", e))?;
259
260 let balance_changes = executed_transaction
261 .balance_changes
262 .iter()
263 .map(TryInto::try_into)
264 .collect::<Result<_, _>>()?;
265
266 let objects = executed_transaction
267 .objects()
268 .try_into()
269 .map_err(|e| TryFromProtoError::invalid("objects.bcs", e))?;
270
271 TransactionExecutionResponse {
272 effects,
273 events,
274 balance_changes,
275 objects,
276 }
277 .pipe(Ok)
278}
279
280fn status_from_error_with_metadata<T: Into<BoxError>>(err: T, metadata: MetadataMap) -> Status {
281 let mut status = Status::from_error(err.into());
282 *status.metadata_mut() = metadata;
283 status
284}