1use std::fmt::{Debug, Display};
7
8use near_account_id::AccountId;
9use near_crypto::PublicKey;
10use near_jsonrpc_client::methods::query::RpcQueryResponse;
11use near_jsonrpc_client::methods::{self, RpcMethod};
12use near_jsonrpc_primitives::types::chunks::ChunkReference;
13use near_jsonrpc_primitives::types::query::QueryResponseKind;
14use near_primitives::borsh;
15use near_primitives::hash::CryptoHash;
16use near_primitives::types::{BlockHeight, BlockId, BlockReference, Finality, ShardId, StoreKey};
17use near_primitives::views::{
18 AccessKeyList, AccessKeyView, AccountView, BlockView, CallResult, ChunkView, QueryRequest,
19 ViewStateResult,
20};
21use near_token::NearToken;
22
23use crate::ops::Function;
24use crate::{Client, Error, Result};
25
26pub(crate) type BoxFuture<'a, T> =
28 std::pin::Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;
29
30pub struct Query<'a, T> {
35 pub(crate) method: T,
36 pub(crate) client: &'a Client,
37 pub(crate) block_ref: Option<BlockReference>,
38}
39
40impl<'a, T> Query<'a, T> {
41 pub(crate) fn new(client: &'a Client, method: T) -> Self {
42 Self {
43 method,
44 client,
45 block_ref: None,
46 }
47 }
48
49 pub fn block_height(mut self, height: BlockHeight) -> Self {
53 self.block_ref = Some(BlockId::Height(height).into());
54 self
55 }
56
57 pub fn block_hash(mut self, hash: CryptoHash) -> Self {
61 self.block_ref = Some(BlockId::Hash(near_primitives::hash::CryptoHash(hash.0)).into());
62 self
63 }
64}
65
66impl<'a, T> Query<'a, T>
68where
69 T: ProcessQuery<Method = methods::query::RpcQueryRequest>,
70{
71 pub fn finality(mut self, value: Finality) -> Self {
73 self.block_ref = Some(value.into());
74 self
75 }
76}
77
78impl<'a, T, R> std::future::IntoFuture for Query<'a, T>
79where
80 T: ProcessQuery<Output = R> + Send + Sync + 'static,
81 <T as ProcessQuery>::Method: RpcMethod + Debug + Send + Sync,
82 <<T as ProcessQuery>::Method as RpcMethod>::Response: Debug + Send + Sync,
83 <<T as ProcessQuery>::Method as RpcMethod>::Error: Debug + Display + Send + Sync,
84{
85 type Output = Result<R>;
86
87 type IntoFuture = BoxFuture<'a, Self::Output>;
90
91 fn into_future(self) -> Self::IntoFuture {
92 Box::pin(async move {
93 let block_reference = self.block_ref.unwrap_or_else(BlockReference::latest);
94 let resp = self
95 .client
96 .send_query(&self.method.into_request(block_reference)?)
97 .await
98 .map_err(|err| Error::Rpc(err.into()))?;
99
100 T::from_response(resp)
101 })
102 }
103}
104
105pub trait ProcessQuery {
109 type Method: RpcMethod;
113
114 type Output;
116
117 fn into_request(self, block_ref: BlockReference) -> Result<Self::Method>;
119
120 fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output>;
122}
123
124pub struct ViewFunction {
125 pub(crate) account_id: AccountId,
126 pub(crate) function: Function,
127}
128
129pub struct ViewCode {
130 pub(crate) account_id: AccountId,
131}
132
133pub struct ViewAccount {
134 pub(crate) account_id: AccountId,
135}
136
137pub struct ViewBlock;
138
139pub struct ViewState {
140 account_id: AccountId,
141 prefix: Option<Vec<u8>>,
142}
143
144pub struct ViewAccessKey {
145 pub(crate) account_id: AccountId,
146 pub(crate) public_key: PublicKey,
147}
148
149pub struct ViewAccessKeyList {
150 pub(crate) account_id: AccountId,
151}
152
153pub struct GasPrice;
154
155impl ProcessQuery for ViewFunction {
156 type Method = methods::query::RpcQueryRequest;
157 type Output = ViewResult;
158
159 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
160 Ok(Self::Method {
161 block_reference,
162 request: QueryRequest::CallFunction {
163 account_id: self.account_id,
164 method_name: self.function.name,
165 args: self.function.args?.into(),
166 },
167 })
168 }
169
170 fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
171 match resp.kind {
172 QueryResponseKind::CallResult(result) => Ok(result.into()),
173 _ => Err(Error::RpcReturnedInvalidData(
174 "while querying account".into(),
175 )),
176 }
177 }
178}
179
180impl Query<'_, ViewFunction> {
182 pub fn args(mut self, args: Vec<u8>) -> Self {
186 self.method.function = self.method.function.args(args);
187 self
188 }
189
190 pub fn args_json<U: serde::Serialize>(mut self, args: U) -> Self {
194 self.method.function = self.method.function.args_json(args);
195 self
196 }
197
198 pub fn args_borsh<U: borsh::BorshSerialize>(mut self, args: U) -> Self {
201 self.method.function = self.method.function.args_borsh(args);
202 self
203 }
204}
205
206impl ProcessQuery for ViewCode {
207 type Method = methods::query::RpcQueryRequest;
208 type Output = Vec<u8>;
209
210 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
211 Ok(Self::Method {
212 block_reference,
213 request: QueryRequest::ViewCode {
214 account_id: self.account_id,
215 },
216 })
217 }
218
219 fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
220 match resp.kind {
221 QueryResponseKind::ViewCode(contract) => Ok(contract.code),
222 _ => Err(Error::RpcReturnedInvalidData("while querying code".into())),
223 }
224 }
225}
226
227impl ProcessQuery for ViewAccount {
228 type Method = methods::query::RpcQueryRequest;
229 type Output = AccountView;
230
231 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
232 Ok(Self::Method {
233 block_reference,
234 request: QueryRequest::ViewAccount {
235 account_id: self.account_id,
236 },
237 })
238 }
239
240 fn from_response(resp: RpcQueryResponse) -> Result<Self::Output> {
241 match resp.kind {
242 QueryResponseKind::ViewAccount(account) => Ok(account),
243 _ => Err(Error::RpcReturnedInvalidData(
244 "while querying account".into(),
245 )),
246 }
247 }
248}
249
250impl ProcessQuery for ViewBlock {
251 type Method = methods::block::RpcBlockRequest;
252 type Output = BlockView;
253
254 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
255 Ok(Self::Method { block_reference })
256 }
257
258 fn from_response(view: BlockView) -> Result<Self::Output> {
259 Ok(view)
260 }
261}
262
263impl ProcessQuery for ViewState {
264 type Method = methods::query::RpcQueryRequest;
265 type Output = ViewStateResult;
266
267 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
268 Ok(Self::Method {
269 block_reference,
270 request: QueryRequest::ViewState {
271 account_id: self.account_id,
272 prefix: StoreKey::from(self.prefix.unwrap_or_default()),
273 include_proof: false,
274 },
275 })
276 }
277
278 fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
279 match resp.kind {
280 QueryResponseKind::ViewState(state) => Ok(state),
281 _ => Err(Error::RpcReturnedInvalidData("while querying state".into())),
282 }
283 }
284}
285
286impl<'a> Query<'a, ViewState> {
287 pub(crate) fn view_state(client: &'a Client, id: &AccountId) -> Self {
288 Self::new(
289 client,
290 ViewState {
291 account_id: id.clone(),
292 prefix: None,
293 },
294 )
295 }
296
297 pub fn prefix(mut self, value: &[u8]) -> Self {
299 self.method.prefix = Some(value.into());
300 self
301 }
302}
303
304impl ProcessQuery for ViewAccessKey {
305 type Method = methods::query::RpcQueryRequest;
306 type Output = AccessKeyView;
307
308 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
309 Ok(Self::Method {
310 block_reference,
311 request: QueryRequest::ViewAccessKey {
312 account_id: self.account_id,
313 public_key: self.public_key,
314 },
315 })
316 }
317
318 fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
319 match resp.kind {
320 QueryResponseKind::AccessKey(key) => Ok(key),
321 _ => Err(Error::RpcReturnedInvalidData(
322 "while querying access key".into(),
323 )),
324 }
325 }
326}
327
328impl ProcessQuery for ViewAccessKeyList {
329 type Method = methods::query::RpcQueryRequest;
330 type Output = AccessKeyList;
331
332 fn into_request(self, block_reference: BlockReference) -> Result<Self::Method> {
333 Ok(Self::Method {
334 block_reference,
335 request: QueryRequest::ViewAccessKeyList {
336 account_id: self.account_id,
337 },
338 })
339 }
340
341 fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
342 match resp.kind {
343 QueryResponseKind::AccessKeyList(keylist) => Ok(keylist),
344 _ => Err(Error::RpcReturnedInvalidData(
345 "while querying access keys".into(),
346 )),
347 }
348 }
349}
350
351impl ProcessQuery for GasPrice {
352 type Method = methods::gas_price::RpcGasPriceRequest;
353 type Output = NearToken;
354
355 fn into_request(self, block_ref: BlockReference) -> Result<Self::Method> {
356 let block_id = match block_ref {
357 BlockReference::BlockId(block_id) => Some(block_id),
359 BlockReference::Finality(_finality) => None,
361 BlockReference::SyncCheckpoint(point) => {
363 return Err(Error::RpcReturnedInvalidData(format!(
364 "Cannot supply sync checkpoint to gas price: {point:?}. Potential API bug?"
365 )))
366 }
367 };
368
369 Ok(Self::Method { block_id })
370 }
371
372 fn from_response(resp: <Self::Method as RpcMethod>::Response) -> Result<Self::Output> {
373 Ok(NearToken::from_yoctonear(resp.gas_price))
374 }
375}
376
377pub struct QueryChunk<'a> {
384 client: &'a Client,
385 chunk_ref: Option<ChunkReference>,
386}
387
388impl<'a> QueryChunk<'a> {
389 pub(crate) fn new(client: &'a Client) -> Self {
390 Self {
391 client,
392 chunk_ref: None,
393 }
394 }
395
396 pub fn block_hash_and_shard(mut self, hash: CryptoHash, shard_id: ShardId) -> Self {
400 self.chunk_ref = Some(ChunkReference::BlockShardId {
401 block_id: BlockId::Hash(near_primitives::hash::CryptoHash(hash.0)),
402 shard_id,
403 });
404 self
405 }
406
407 pub fn block_height_and_shard(mut self, height: BlockHeight, shard_id: ShardId) -> Self {
411 self.chunk_ref = Some(ChunkReference::BlockShardId {
412 block_id: BlockId::Height(height),
413 shard_id,
414 });
415 self
416 }
417
418 pub fn chunk_hash(mut self, hash: CryptoHash) -> Self {
420 self.chunk_ref = Some(ChunkReference::ChunkHash {
421 chunk_id: near_primitives::hash::CryptoHash(hash.0),
422 });
423 self
424 }
425}
426
427impl<'a> std::future::IntoFuture for QueryChunk<'a> {
428 type Output = Result<ChunkView>;
429 type IntoFuture = BoxFuture<'a, Self::Output>;
430
431 fn into_future(self) -> Self::IntoFuture {
432 Box::pin(async move {
433 let chunk_reference = if let Some(chunk_ref) = self.chunk_ref {
434 chunk_ref
435 } else {
436 let block_view = self.client.view_block().await?;
439 ChunkReference::BlockShardId {
440 block_id: BlockId::Hash(block_view.header.hash),
441 shard_id: ShardId::from(0),
442 }
443 };
444
445 let chunk_view = self
446 .client
447 .send_query(&methods::chunk::RpcChunkRequest { chunk_reference })
448 .await?;
449
450 Ok(chunk_view)
451 })
452 }
453}
454
455#[derive(Clone, Debug, PartialEq, Eq)]
459#[non_exhaustive]
460pub struct ViewResult {
461 pub result: Vec<u8>,
463 pub logs: Vec<String>,
465}
466
467impl ViewResult {
468 pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T> {
473 Ok(serde_json::from_slice(&self.result)?)
474 }
475
476 pub fn borsh<T: borsh::BorshDeserialize>(&self) -> Result<T> {
480 Ok(borsh::BorshDeserialize::try_from_slice(&self.result)?)
481 }
482}
483
484impl From<CallResult> for ViewResult {
485 fn from(result: CallResult) -> Self {
486 Self {
487 result: result.result,
488 logs: result.logs,
489 }
490 }
491}
492
493impl Client {
494 pub fn view(&self, contract_id: &AccountId, function: &str) -> Query<'_, ViewFunction> {
498 Query::new(
499 self,
500 ViewFunction {
501 account_id: contract_id.clone(),
502 function: Function::new(function),
503 },
504 )
505 }
506
507 pub fn view_code(&self, contract_id: &AccountId) -> Query<'_, ViewCode> {
509 Query::new(
510 self,
511 ViewCode {
512 account_id: contract_id.clone(),
513 },
514 )
515 }
516
517 pub fn view_state(&self, contract_id: &AccountId) -> Query<'_, ViewState> {
521 Query::view_state(self, contract_id)
522 }
523
524 pub fn view_block(&self) -> Query<'_, ViewBlock> {
530 Query::new(self, ViewBlock)
531 }
532
533 pub fn view_chunk(&self) -> QueryChunk<'_> {
543 QueryChunk::new(self)
544 }
545
546 pub fn view_access_key(&self, id: &AccountId, pk: &PublicKey) -> Query<'_, ViewAccessKey> {
551 Query::new(
552 self,
553 ViewAccessKey {
554 account_id: id.clone(),
555 public_key: pk.clone(),
556 },
557 )
558 }
559
560 pub fn view_access_keys(&self, id: &AccountId) -> Query<'_, ViewAccessKeyList> {
565 Query::new(
566 self,
567 ViewAccessKeyList {
568 account_id: id.clone(),
569 },
570 )
571 }
572
573 pub fn view_account(&self, account_id: &AccountId) -> Query<'_, ViewAccount> {
575 Query::new(
576 self,
577 ViewAccount {
578 account_id: account_id.clone(),
579 },
580 )
581 }
582
583 pub fn gas_price(&self) -> Query<'_, GasPrice> {
585 Query::new(self, GasPrice)
586 }
587}