├── CODEOWNERS ├── .gitignore ├── src ├── methods │ ├── sandbox │ │ ├── mod.rs │ │ ├── fast_forward.rs │ │ └── patch_state.rs │ ├── adversarial │ │ ├── disable_doomslug.rs │ │ ├── disable_header_sync.rs │ │ ├── set_weight.rs │ │ ├── switch_to_height.rs │ │ ├── produce_blocks.rs │ │ ├── mod.rs │ │ ├── check_store.rs │ │ └── get_saved_blocks.rs │ ├── experimental │ │ ├── mod.rs │ │ ├── genesis_config.rs │ │ ├── validators_ordered.rs │ │ ├── receipt.rs │ │ ├── protocol_config.rs │ │ ├── changes_in_block.rs │ │ ├── tx_status.rs │ │ └── changes.rs │ ├── health.rs │ ├── status.rs │ ├── network_info.rs │ ├── next_light_client_block.rs │ ├── light_client_proof.rs │ ├── validators.rs │ ├── gas_price.rs │ ├── block.rs │ ├── tx.rs │ ├── any │ │ └── mod.rs │ ├── send_tx.rs │ ├── broadcast_tx_commit.rs │ ├── broadcast_tx_async.rs │ ├── chunk.rs │ ├── mod.rs │ └── query.rs ├── header.rs ├── auth.rs ├── errors.rs └── lib.rs ├── rust-toolchain.toml ├── examples ├── view_account.rs ├── contract_view_state.rs ├── contract_view_code.rs ├── contract_view_method.rs ├── access_keys.rs ├── auth.rs ├── contract_change_method_commit.rs ├── utils.rs ├── query_block.rs ├── contract_change_method.rs ├── send_tx.rs ├── query_tx.rs └── create_account.rs ├── LICENSE-MIT ├── Cargo.toml ├── .github └── workflows │ └── ci.yml ├── README.md ├── LICENSE-APACHE └── CHANGELOG.md /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dj8yfo @akorchyn @PolyProgrammist 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust artifacts 2 | /target 3 | Cargo.lock 4 | 5 | # IDEs 6 | .idea 7 | .vscode 8 | 9 | # macOS 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /src/methods/sandbox/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod patch_state; 4 | pub use patch_state as sandbox_patch_state; 5 | 6 | pub mod fast_forward; 7 | pub use fast_forward as sandbox_fast_forward; 8 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | # This specifies the version of Rust we use to build. 3 | # Individual crates in the workspace may support a lower version, as indicated by `rust-version` field in each crate's `Cargo.toml`. 4 | # The version specified below, should be at least as high as the maximum `rust-version` within the workspace. 5 | channel = "stable" 6 | components = ["rustfmt", "clippy", "rust-analyzer"] 7 | -------------------------------------------------------------------------------- /src/methods/adversarial/disable_doomslug.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug)] 4 | pub struct RpcAdversarialDisableDoomslugRequest; 5 | 6 | impl RpcMethod for RpcAdversarialDisableDoomslugRequest { 7 | type Response = (); 8 | type Error = (); 9 | 10 | fn method_name(&self) -> &str { 11 | "adv_disable_doomslug" 12 | } 13 | 14 | fn params(&self) -> Result { 15 | Ok(json!(null)) 16 | } 17 | } 18 | 19 | impl private::Sealed for RpcAdversarialDisableDoomslugRequest {} 20 | -------------------------------------------------------------------------------- /src/methods/adversarial/disable_header_sync.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug)] 4 | pub struct RpcAdversarialDisableHeaderSyncRequest; 5 | 6 | impl RpcMethod for RpcAdversarialDisableHeaderSyncRequest { 7 | type Response = (); 8 | type Error = (); 9 | 10 | fn method_name(&self) -> &str { 11 | "adv_disable_header_sync" 12 | } 13 | 14 | fn params(&self) -> Result { 15 | Ok(json!(null)) 16 | } 17 | } 18 | 19 | impl private::Sealed for RpcAdversarialDisableHeaderSyncRequest {} 20 | -------------------------------------------------------------------------------- /src/methods/adversarial/set_weight.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug)] 4 | pub struct RpcAdversarialSetWeightRequest { 5 | pub height: u64, 6 | } 7 | 8 | impl RpcMethod for RpcAdversarialSetWeightRequest { 9 | type Response = (); 10 | type Error = (); 11 | 12 | fn method_name(&self) -> &str { 13 | "adv_set_weight" 14 | } 15 | 16 | fn params(&self) -> Result { 17 | Ok(json!(self.height)) 18 | } 19 | } 20 | 21 | impl private::Sealed for RpcAdversarialSetWeightRequest {} 22 | -------------------------------------------------------------------------------- /src/methods/adversarial/switch_to_height.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug)] 4 | pub struct RpcAdversarialSwitchToHeightRequest { 5 | pub height: u64, 6 | } 7 | 8 | impl RpcMethod for RpcAdversarialSwitchToHeightRequest { 9 | type Response = (); 10 | type Error = (); 11 | 12 | fn method_name(&self) -> &str { 13 | "adv_switch_to_height" 14 | } 15 | 16 | fn params(&self) -> Result { 17 | Ok(json!([self.height])) 18 | } 19 | } 20 | 21 | impl private::Sealed for RpcAdversarialSwitchToHeightRequest {} 22 | -------------------------------------------------------------------------------- /src/methods/adversarial/produce_blocks.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug)] 4 | pub struct RpcAdversarialProduceBlocksRequest { 5 | pub num_blocks: u64, 6 | pub only_valid: bool, 7 | } 8 | 9 | impl RpcMethod for RpcAdversarialProduceBlocksRequest { 10 | type Response = (); 11 | type Error = (); 12 | 13 | fn method_name(&self) -> &str { 14 | "adv_produce_blocks" 15 | } 16 | 17 | fn params(&self) -> Result { 18 | Ok(json!([self.num_blocks, self.only_valid])) 19 | } 20 | } 21 | 22 | impl private::Sealed for RpcAdversarialProduceBlocksRequest {} 23 | -------------------------------------------------------------------------------- /src/methods/adversarial/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod check_store; 4 | pub use check_store as adv_check_store; 5 | 6 | pub mod disable_doomslug; 7 | pub use disable_doomslug as adv_disable_doomslug; 8 | 9 | pub mod disable_header_sync; 10 | pub use disable_header_sync as adv_disable_header_sync; 11 | 12 | pub mod get_saved_blocks; 13 | pub use get_saved_blocks as adv_get_saved_blocks; 14 | 15 | pub mod produce_blocks; 16 | pub use produce_blocks as adv_produce_blocks; 17 | 18 | pub mod set_weight; 19 | pub use set_weight as adv_set_weight; 20 | 21 | pub mod switch_to_height; 22 | pub use switch_to_height as adv_switch_to_height; 23 | -------------------------------------------------------------------------------- /src/methods/experimental/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod changes; 4 | pub use changes as EXPERIMENTAL_changes; 5 | 6 | pub mod changes_in_block; 7 | pub use changes_in_block as EXPERIMENTAL_changes_in_block; 8 | 9 | pub mod genesis_config; 10 | pub use genesis_config as EXPERIMENTAL_genesis_config; 11 | 12 | pub mod protocol_config; 13 | pub use protocol_config as EXPERIMENTAL_protocol_config; 14 | 15 | pub mod receipt; 16 | pub use receipt as EXPERIMENTAL_receipt; 17 | 18 | pub mod tx_status; 19 | pub use tx_status as EXPERIMENTAL_tx_status; 20 | 21 | pub mod validators_ordered; 22 | pub use validators_ordered as EXPERIMENTAL_validators_ordered; 23 | -------------------------------------------------------------------------------- /src/methods/adversarial/check_store.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use serde::Deserialize; 4 | 5 | #[derive(Debug)] 6 | pub struct RpcAdversarialCheckStoreRequest; 7 | 8 | #[derive(Debug, Deserialize)] 9 | pub struct RpcAdversarialCheckStoreResponse(pub u64); 10 | 11 | impl RpcHandlerResponse for RpcAdversarialCheckStoreResponse {} 12 | 13 | impl RpcMethod for RpcAdversarialCheckStoreRequest { 14 | type Response = RpcAdversarialCheckStoreResponse; 15 | type Error = (); 16 | 17 | fn method_name(&self) -> &str { 18 | "adv_check_store" 19 | } 20 | 21 | fn params(&self) -> Result { 22 | Ok(json!(null)) 23 | } 24 | } 25 | 26 | impl private::Sealed for RpcAdversarialCheckStoreRequest {} 27 | -------------------------------------------------------------------------------- /src/methods/adversarial/get_saved_blocks.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use serde::Deserialize; 4 | 5 | #[derive(Debug)] 6 | pub struct RpcAdversarialGetSavedBlocksRequest; 7 | 8 | #[derive(Debug, Deserialize)] 9 | pub struct RpcAdversarialGetSavedBlocksResponse(pub u64); 10 | 11 | impl RpcHandlerResponse for RpcAdversarialGetSavedBlocksResponse {} 12 | 13 | impl RpcMethod for RpcAdversarialGetSavedBlocksRequest { 14 | type Response = RpcAdversarialGetSavedBlocksResponse; 15 | type Error = (); 16 | 17 | fn method_name(&self) -> &str { 18 | "adv_get_saved_blocks" 19 | } 20 | 21 | fn params(&self) -> Result { 22 | Ok(json!(null)) 23 | } 24 | } 25 | 26 | impl private::Sealed for RpcAdversarialGetSavedBlocksRequest {} 27 | -------------------------------------------------------------------------------- /examples/view_account.rs: -------------------------------------------------------------------------------- 1 | use near_jsonrpc_client::methods; 2 | use near_jsonrpc_primitives::types::query::QueryResponseKind; 3 | use near_primitives::types::{AccountId, BlockReference, Finality}; 4 | use near_primitives::views::QueryRequest; 5 | 6 | mod utils; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<(), Box> { 10 | env_logger::init(); 11 | 12 | let client = utils::select_network()?; 13 | 14 | let account_id: AccountId = utils::input("Enter an Account ID to lookup: ")?.parse()?; 15 | 16 | let request = methods::query::RpcQueryRequest { 17 | block_reference: BlockReference::Finality(Finality::Final), 18 | request: QueryRequest::ViewAccount { account_id }, 19 | }; 20 | 21 | let response = client.call(request).await?; 22 | 23 | if let QueryResponseKind::ViewAccount(result) = response.kind { 24 | println!("{:#?}", result); 25 | } 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /examples/contract_view_state.rs: -------------------------------------------------------------------------------- 1 | use near_jsonrpc_client::methods; 2 | use near_jsonrpc_primitives::types::query::QueryResponseKind; 3 | use near_primitives::types::{AccountId, BlockReference, Finality}; 4 | use near_primitives::views::QueryRequest; 5 | 6 | mod utils; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<(), Box> { 10 | env_logger::init(); 11 | 12 | let client = utils::select_network()?; 13 | 14 | let contract_id: AccountId = 15 | utils::input("Enter the contract whose state you want to inspect: ")?.parse()?; 16 | 17 | let request = methods::query::RpcQueryRequest { 18 | block_reference: BlockReference::Finality(Finality::Final), 19 | request: QueryRequest::ViewState { 20 | account_id: contract_id.clone(), 21 | prefix: near_primitives::types::StoreKey::from(Vec::new()), 22 | include_proof: false, 23 | }, 24 | }; 25 | 26 | let response = client.call(request).await?; 27 | 28 | if let QueryResponseKind::ViewState(result) = response.kind { 29 | println!("{:#?}", result); 30 | } 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/contract_view_code.rs: -------------------------------------------------------------------------------- 1 | use near_jsonrpc_client::methods; 2 | use near_jsonrpc_primitives::types::query::QueryResponseKind; 3 | use near_primitives::types::{AccountId, BlockReference, Finality}; 4 | use near_primitives::views::QueryRequest; 5 | 6 | mod utils; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<(), Box> { 10 | env_logger::init(); 11 | 12 | let client = utils::select_network()?; 13 | 14 | let contract_id: AccountId = 15 | utils::input("Enter the contract whose code we're downloading: ")?.parse()?; 16 | 17 | let request = methods::query::RpcQueryRequest { 18 | block_reference: BlockReference::Finality(Finality::Final), 19 | request: QueryRequest::ViewCode { 20 | account_id: contract_id.clone(), 21 | }, 22 | }; 23 | 24 | let response = client.call(request).await?; 25 | 26 | if let QueryResponseKind::ViewCode(result) = response.kind { 27 | let path = format!("/tmp/{}.wasm", contract_id); 28 | println!("⚙️ [{}]", contract_id); 29 | println!("🏋 size: {} bytes", result.code.len()); 30 | std::fs::write(&path, result.code)?; 31 | println!("💾 saved to: {}", path); 32 | } 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "near-jsonrpc-client" 3 | version = "0.20.0" 4 | authors = ["Near Inc "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/near/near-jsonrpc-client-rs" 8 | description = "Lower-level API for interfacing with the NEAR Protocol via JSONRPC" 9 | categories = ["asynchronous", "api-bindings", "network-programming"] 10 | keywords = ["near", "api", "jsonrpc", "rpc", "async"] 11 | rust-version = "1.67.1" 12 | 13 | [dependencies] 14 | log = "0.4.17" 15 | borsh = "1.3.0" 16 | serde = "1.0.145" 17 | reqwest = { version = "0.12", features = ["json"], default-features = false } 18 | thiserror = "2.0" 19 | serde_json = "1.0.85" 20 | lazy_static = "1.4.0" 21 | 22 | near-crypto = "0.34" 23 | near-primitives = { version = "0.34", features = ["test_utils"] } 24 | near-chain-configs = "0.34" 25 | near-jsonrpc-primitives = "0.34" 26 | 27 | [dev-dependencies] 28 | tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } 29 | env_logger = "0.11.0" 30 | 31 | [features] 32 | default = ["native-tls"] 33 | any = [] 34 | sandbox = [] 35 | adversarial = [] 36 | native-tls = ["reqwest/native-tls"] 37 | rustls-tls = ["reqwest/rustls-tls"] 38 | 39 | [[example]] 40 | name = "auth" 41 | 42 | [package.metadata.docs.rs] 43 | features = ["any", "sandbox"] 44 | -------------------------------------------------------------------------------- /src/methods/health.rs: -------------------------------------------------------------------------------- 1 | //! Requests the health status of the RPC node. 2 | //! 3 | //! ## Example 4 | //! 5 | //! Returns the current health stauts of the RPC node the client connects to. 6 | //! 7 | //! ``` 8 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 9 | //! 10 | //! # #[tokio::main] 11 | //! # async fn main() -> Result<(), Box> { 12 | //! let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 13 | //! 14 | //! let request = methods::health::RpcHealthRequest; 15 | //! 16 | //! let response = client.call(request).await?; 17 | //! 18 | //! assert!(matches!( 19 | //! response, 20 | //! methods::health::RpcHealthResponse 21 | //! )); 22 | //! # Ok(()) 23 | //! # } 24 | //! ``` 25 | use super::*; 26 | 27 | pub use near_jsonrpc_primitives::types::status::{RpcHealthResponse, RpcStatusError}; 28 | 29 | #[derive(Debug)] 30 | pub struct RpcHealthRequest; 31 | 32 | impl RpcHandlerResponse for RpcHealthResponse {} 33 | 34 | impl RpcMethod for RpcHealthRequest { 35 | type Response = RpcHealthResponse; 36 | type Error = RpcStatusError; 37 | 38 | fn method_name(&self) -> &str { 39 | "health" 40 | } 41 | 42 | fn params(&self) -> Result { 43 | Ok(json!(null)) 44 | } 45 | } 46 | 47 | impl private::Sealed for RpcHealthRequest {} 48 | -------------------------------------------------------------------------------- /src/methods/status.rs: -------------------------------------------------------------------------------- 1 | //! Requests the status of the connected RPC node. 2 | //! 3 | //! This includes information about sync status, nearcore node version, protocol version, the current set of validators, etc. 4 | //! 5 | //! ## Example 6 | //! 7 | //! ``` 8 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 9 | //! 10 | //! # #[tokio::main] 11 | //! # async fn main() -> Result<(), Box> { 12 | //! let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 13 | //! 14 | //! let request = methods::status::RpcStatusRequest; 15 | //! 16 | //! let response = client.call(request).await?; 17 | //! 18 | //! assert!(matches!( 19 | //! response, 20 | //! methods::status::RpcStatusResponse { .. } 21 | //! )); 22 | //! # Ok(()) 23 | //! # } 24 | //! ``` 25 | use super::*; 26 | 27 | pub use near_jsonrpc_primitives::types::status::RpcStatusError; 28 | 29 | pub type RpcStatusResponse = near_primitives::views::StatusResponse; 30 | 31 | #[derive(Debug)] 32 | pub struct RpcStatusRequest; 33 | 34 | impl RpcHandlerResponse for RpcStatusResponse {} 35 | 36 | impl RpcMethod for RpcStatusRequest { 37 | type Response = RpcStatusResponse; 38 | type Error = RpcStatusError; 39 | 40 | fn method_name(&self) -> &str { 41 | "status" 42 | } 43 | 44 | fn params(&self) -> Result { 45 | Ok(json!(null)) 46 | } 47 | } 48 | 49 | impl private::Sealed for RpcStatusRequest {} 50 | -------------------------------------------------------------------------------- /src/methods/network_info.rs: -------------------------------------------------------------------------------- 1 | //! Queries the current state of node network connections. 2 | //! 3 | //! This includes information about active peers, transmitted data, known producers, etc. 4 | //! 5 | //! ## Example 6 | //! 7 | //! ``` 8 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 9 | //! 10 | //! # #[tokio::main] 11 | //! # async fn main() -> Result<(), Box> { 12 | //! let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 13 | //! 14 | //! let request = methods::network_info::RpcNetworkInfoRequest; 15 | //! 16 | //! let response = client.call(request).await; 17 | //! 18 | //! assert!(matches!( 19 | //! response, 20 | //! Ok(methods::network_info::RpcNetworkInfoResponse { .. }) 21 | //! )); 22 | //! # Ok(()) 23 | //! # } 24 | //! ``` 25 | use super::*; 26 | 27 | pub use near_jsonrpc_primitives::types::network_info::{ 28 | RpcNetworkInfoError, RpcNetworkInfoResponse, 29 | }; 30 | 31 | #[derive(Debug)] 32 | pub struct RpcNetworkInfoRequest; 33 | 34 | impl RpcHandlerResponse for RpcNetworkInfoResponse {} 35 | 36 | impl RpcHandlerError for RpcNetworkInfoError {} 37 | 38 | impl RpcMethod for RpcNetworkInfoRequest { 39 | type Response = RpcNetworkInfoResponse; 40 | type Error = RpcNetworkInfoError; 41 | 42 | fn method_name(&self) -> &str { 43 | "network_info" 44 | } 45 | 46 | fn params(&self) -> Result { 47 | Ok(json!(null)) 48 | } 49 | } 50 | 51 | impl private::Sealed for RpcNetworkInfoRequest {} 52 | -------------------------------------------------------------------------------- /examples/contract_view_method.rs: -------------------------------------------------------------------------------- 1 | use near_jsonrpc_client::{methods, JsonRpcClient}; 2 | use near_jsonrpc_primitives::types::query::QueryResponseKind; 3 | use near_primitives::types::{BlockReference, Finality, FunctionArgs}; 4 | use near_primitives::views::QueryRequest; 5 | 6 | use serde::Deserialize; 7 | use serde_json::{from_slice, json}; 8 | 9 | mod utils; 10 | 11 | #[derive(Debug, Deserialize)] 12 | pub struct AccountStatus { 13 | pub rating: f32, 14 | pub given: u64, 15 | pub received: u64, 16 | } 17 | 18 | #[tokio::main] 19 | async fn main() -> Result<(), Box> { 20 | env_logger::init(); 21 | 22 | let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 23 | 24 | let account_id = utils::input("Enter the account to view: ")?; 25 | 26 | let request = methods::query::RpcQueryRequest { 27 | block_reference: BlockReference::Finality(Finality::Final), 28 | request: QueryRequest::CallFunction { 29 | account_id: "nosedive.testnet".parse()?, 30 | method_name: "status".to_string(), 31 | args: FunctionArgs::from( 32 | json!({ 33 | "account_id": account_id, 34 | }) 35 | .to_string() 36 | .into_bytes(), 37 | ), 38 | }, 39 | }; 40 | 41 | let response = client.call(request).await?; 42 | 43 | if let QueryResponseKind::CallResult(result) = response.kind { 44 | println!("{:#?}", from_slice::(&result.result)?); 45 | } 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /src/methods/sandbox/fast_forward.rs: -------------------------------------------------------------------------------- 1 | //! Fast forwards a sandboxed node by a specific height. 2 | //! 3 | //! Fas forwarding allows one to skip to some point in the future and observe actions. 4 | //! 5 | //! ## Example 6 | //! 7 | //! ``` 8 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 9 | //! 10 | //! # #[tokio::main] 11 | //! # async fn main() -> Result<(), Box> { 12 | //! let client = JsonRpcClient::connect("http://localhost:3030"); 13 | //! 14 | //! let request = methods::sandbox_fast_forward::RpcSandboxFastForwardRequest { 15 | //! delta_height: 12, 16 | //! }; 17 | //! 18 | //! let response = client.call(request).await?; 19 | //! 20 | //! assert!(matches!( 21 | //! response, 22 | //! methods::sandbox_fast_forward::RpcSandboxFastForwardResponse { .. } 23 | //! )); 24 | //! # Ok(()) 25 | //! # } 26 | //! ``` 27 | use super::*; 28 | 29 | pub use near_jsonrpc_primitives::types::sandbox::{ 30 | RpcSandboxFastForwardError, RpcSandboxFastForwardRequest, RpcSandboxFastForwardResponse, 31 | }; 32 | 33 | impl RpcHandlerResponse for RpcSandboxFastForwardResponse {} 34 | 35 | impl RpcHandlerError for RpcSandboxFastForwardError {} 36 | 37 | impl RpcMethod for RpcSandboxFastForwardRequest { 38 | type Response = RpcSandboxFastForwardResponse; 39 | type Error = RpcSandboxFastForwardError; 40 | 41 | fn method_name(&self) -> &str { 42 | "sandbox_fast_forward" 43 | } 44 | 45 | fn params(&self) -> Result { 46 | Ok(json!(self)) 47 | } 48 | } 49 | 50 | impl private::Sealed for RpcSandboxFastForwardRequest {} 51 | -------------------------------------------------------------------------------- /examples/access_keys.rs: -------------------------------------------------------------------------------- 1 | use near_jsonrpc_client::methods; 2 | use near_jsonrpc_primitives::types::query::QueryResponseKind; 3 | use near_primitives::types::BlockReference; 4 | 5 | mod utils; 6 | 7 | fn indent(indentation: usize, s: String) -> String { 8 | let mut lines = s.split_inclusive("\n"); 9 | let mut r = lines.next().unwrap().to_string(); 10 | for l in lines { 11 | r.push_str(&" ".repeat(indentation - 3)); 12 | r.push_str("\x1b[38;5;244m>\x1b[0m "); 13 | r.push_str(l); 14 | } 15 | r 16 | } 17 | 18 | #[tokio::main] 19 | async fn main() -> Result<(), Box> { 20 | env_logger::init(); 21 | 22 | let client = utils::select_network()?; 23 | 24 | let account_id = utils::input("Enter the Account ID whose keys we're listing: ")?.parse()?; 25 | 26 | let access_key_query_response = client 27 | .call(methods::query::RpcQueryRequest { 28 | block_reference: BlockReference::latest(), 29 | request: near_primitives::views::QueryRequest::ViewAccessKeyList { account_id }, 30 | }) 31 | .await?; 32 | 33 | if let QueryResponseKind::AccessKeyList(response) = access_key_query_response.kind { 34 | for access_key in response.keys { 35 | println!("🗝 [{}]", access_key.public_key); 36 | println!(" \u{21b3} nonce: {}", access_key.access_key.nonce); 37 | println!( 38 | " \u{21b3} permission: {}", 39 | indent(20, format!("{:#?}", access_key.access_key.permission)) 40 | ); 41 | } 42 | } 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /src/methods/experimental/genesis_config.rs: -------------------------------------------------------------------------------- 1 | //! Queries the genesis config of the network. 2 | //! 3 | //! ## Example 4 | //! 5 | //! Returns the genesis config of the network. 6 | //! 7 | //! ``` 8 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 9 | //! 10 | //! # #[tokio::main] 11 | //! # async fn main() -> Result<(), Box> { 12 | //! let client = JsonRpcClient::connect("https://rpc.mainnet.near.org"); 13 | //! 14 | //! let request = methods::EXPERIMENTAL_genesis_config::RpcGenesisConfigRequest; 15 | //! 16 | //! let response = client.call(request).await?; 17 | //! 18 | //! assert!(matches!( 19 | //! response, 20 | //! methods::EXPERIMENTAL_genesis_config::RpcGenesisConfigResponse { .. } 21 | //! )); 22 | //! # Ok(()) 23 | //! # } 24 | //! ``` 25 | use super::*; 26 | 27 | pub type RpcGenesisConfigResponse = near_chain_configs::GenesisConfig; 28 | 29 | #[derive(Debug)] 30 | pub struct RpcGenesisConfigRequest; 31 | 32 | #[derive(Debug, Serialize, Deserialize, Error)] 33 | #[error("{}", unreachable!("fatal: this error should never be constructed"))] 34 | pub enum RpcGenesisConfigError {} 35 | 36 | impl RpcHandlerResponse for RpcGenesisConfigResponse {} 37 | 38 | impl RpcHandlerError for RpcGenesisConfigError {} 39 | 40 | impl RpcMethod for RpcGenesisConfigRequest { 41 | type Response = RpcGenesisConfigResponse; 42 | type Error = RpcGenesisConfigError; 43 | 44 | fn method_name(&self) -> &str { 45 | "EXPERIMENTAL_genesis_config" 46 | } 47 | 48 | fn params(&self) -> Result { 49 | Ok(json!(null)) 50 | } 51 | } 52 | 53 | impl private::Sealed for RpcGenesisConfigRequest {} 54 | -------------------------------------------------------------------------------- /src/methods/experimental/validators_ordered.rs: -------------------------------------------------------------------------------- 1 | //! Returns the ordered validators of a block. 2 | //! 3 | //! ## Example 4 | //! 5 | //! Returns the ordered validators for this [block](https://explorer.near.org/blocks/3Lq3Mtfpc3spH9oF5dXnUzvCBEqjTQwX1yCqKibwzgWR). 6 | //! 7 | //! ``` 8 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 9 | //! use near_primitives::types::BlockId; 10 | //! 11 | //! # #[tokio::main] 12 | //! # async fn main() -> Result<(), Box> { 13 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 14 | //! 15 | //! let request = methods::EXPERIMENTAL_validators_ordered::RpcValidatorsOrderedRequest { 16 | //! block_id: Some(BlockId::Hash("Brj839ta6ffccCvDcXzEh7iRak2jCxuc7M3U1cEmRH9k".parse()?)) 17 | //! }; 18 | //! 19 | //! let response = client.call(request).await?; 20 | //! 21 | //! assert!(matches!( 22 | //! response, 23 | //! methods::EXPERIMENTAL_validators_ordered::RpcValidatorsOrderedResponse { .. } 24 | //! )); 25 | //! # Ok(()) 26 | //! # } 27 | //! ``` 28 | use super::*; 29 | 30 | pub use near_jsonrpc_primitives::types::validator::{ 31 | RpcValidatorError, RpcValidatorsOrderedRequest, RpcValidatorsOrderedResponse, 32 | }; 33 | 34 | impl RpcHandlerResponse for RpcValidatorsOrderedResponse {} 35 | 36 | impl RpcMethod for RpcValidatorsOrderedRequest { 37 | type Response = RpcValidatorsOrderedResponse; 38 | type Error = RpcValidatorError; 39 | 40 | fn method_name(&self) -> &str { 41 | "EXPERIMENTAL_validators_ordered" 42 | } 43 | 44 | fn params(&self) -> Result { 45 | Ok(json!(self)) 46 | } 47 | } 48 | 49 | impl private::Sealed for RpcValidatorsOrderedRequest {} 50 | -------------------------------------------------------------------------------- /src/methods/next_light_client_block.rs: -------------------------------------------------------------------------------- 1 | //! Returns the next light client block. 2 | //! 3 | //! ## Example 4 | //! 5 | //! ``` 6 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 7 | //! 8 | //! # #[tokio::main] 9 | //! # async fn main() -> Result<(), Box> { 10 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 11 | //! 12 | //! let request = methods::next_light_client_block::RpcLightClientNextBlockRequest { 13 | //! last_block_hash: "ANm3jm5wq1Z4rJv6tXWyiDtC3wYKpXVHY4iq6bE1te7B".parse()?, 14 | //! }; 15 | //! 16 | //! let response = client.call(request).await?; 17 | //! 18 | //! assert!(matches!( 19 | //! response, 20 | //! Some(methods::next_light_client_block::LightClientBlockView { .. }) 21 | //! )); 22 | //! # Ok(()) 23 | //! # } 24 | //! ``` 25 | use super::*; 26 | 27 | pub use near_jsonrpc_primitives::types::light_client::{ 28 | RpcLightClientNextBlockError, RpcLightClientNextBlockRequest, 29 | }; 30 | pub use near_primitives::views::LightClientBlockView; 31 | 32 | pub type RpcLightClientNextBlockResponse = Option; 33 | 34 | impl RpcHandlerResponse for RpcLightClientNextBlockResponse {} 35 | 36 | impl RpcHandlerError for RpcLightClientNextBlockError { 37 | fn parse(value: serde_json::Value) -> Result { 38 | common::parse_unknown_block!(value => Self) 39 | } 40 | } 41 | 42 | impl RpcMethod for RpcLightClientNextBlockRequest { 43 | type Response = RpcLightClientNextBlockResponse; 44 | type Error = RpcLightClientNextBlockError; 45 | 46 | fn method_name(&self) -> &str { 47 | "next_light_client_block" 48 | } 49 | 50 | fn params(&self) -> Result { 51 | Ok(json!(self)) 52 | } 53 | } 54 | 55 | impl private::Sealed for RpcLightClientNextBlockRequest {} 56 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | env: 14 | RUSTFLAGS: -D warnings 15 | RUST_BACKTRACE: short 16 | CARGO_NET_RETRY: 10 17 | CARGO_TERM_COLOR: always 18 | CARGO_INCREMENTAL: 0 19 | 20 | jobs: 21 | test: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - name: Checkout Repository 26 | uses: actions/checkout@v2 27 | 28 | - name: Show Active Rust Toolchain 29 | run: rustup show active-toolchain 30 | 31 | - name: Run cargo test 32 | run: cargo test --verbose --workspace 33 | 34 | clippy: 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - name: Checkout Repository 39 | uses: actions/checkout@v2 40 | 41 | - name: Run clippy 42 | run: cargo clippy -- -D clippy::all 43 | 44 | cargo-fmt: 45 | runs-on: ubuntu-latest 46 | 47 | steps: 48 | - name: Checkout Repository 49 | uses: actions/checkout@v2 50 | 51 | - name: Run cargo fmt 52 | run: cargo fmt --all -- --check 53 | 54 | release-plz: 55 | runs-on: ubuntu-latest 56 | needs: [test, clippy, cargo-fmt] 57 | if: github.ref == 'refs/heads/master' 58 | steps: 59 | - name: Checkout repository 60 | uses: actions/checkout@v3 61 | with: 62 | fetch-depth: 0 63 | token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} 64 | - name: Install Rust toolchain 65 | uses: dtolnay/rust-toolchain@stable 66 | - name: Run release-plz 67 | uses: MarcoIeni/release-plz-action@v0.5 68 | env: 69 | # https://marcoieni.github.io/release-plz/github-action.html#triggering-further-workflow-runs 70 | GITHUB_TOKEN: ${{ secrets.CUSTOM_GITHUB_TOKEN }} 71 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 72 | -------------------------------------------------------------------------------- /src/methods/experimental/receipt.rs: -------------------------------------------------------------------------------- 1 | //! Fetches a receipt by it's ID 2 | //! 3 | //! The `RpcReceiptRequest` takes in a [`ReceiptReference`](https://docs.rs/near-jsonrpc-primitives/0.12.0/near_jsonrpc_primitives/types/receipts/struct.ReceiptReference.html) 4 | //! 5 | //! ## Example 6 | //! 7 | //! Returns the receipt for this [transaction](https://explorer.near.org/transactions/4nVcmhWkV8Y3uJp9VQWrJhfesncJERfrvt9WwDi77oEJ#3B5PPT9EKj5352Wks9GnCeSUBDsVvSF4ceMQv2nEULTf) on mainnet. 8 | //! 9 | //! ``` 10 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 11 | //! use near_jsonrpc_primitives::types::receipts::ReceiptReference; 12 | //! 13 | //! # #[tokio::main] 14 | //! # async fn main() -> Result<(), Box> { 15 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 16 | //! 17 | //! let request = methods::EXPERIMENTAL_receipt::RpcReceiptRequest { 18 | //! receipt_reference: ReceiptReference { 19 | //! receipt_id: "3B5PPT9EKj5352Wks9GnCeSUBDsVvSF4ceMQv2nEULTf".parse()?, 20 | //! } 21 | //! }; 22 | //! 23 | //! let response = client.call(request).await?; 24 | //! 25 | //! assert!(matches!( 26 | //! response, 27 | //! methods::EXPERIMENTAL_receipt::RpcReceiptResponse { .. } 28 | //! )); 29 | //! # Ok(()) 30 | //! # } 31 | //! ``` 32 | use super::*; 33 | 34 | pub use near_jsonrpc_primitives::types::receipts::{RpcReceiptError, RpcReceiptRequest}; 35 | 36 | pub type RpcReceiptResponse = near_primitives::views::ReceiptView; 37 | 38 | impl RpcHandlerResponse for RpcReceiptResponse {} 39 | 40 | impl RpcHandlerError for RpcReceiptError {} 41 | 42 | impl RpcMethod for RpcReceiptRequest { 43 | type Response = RpcReceiptResponse; 44 | type Error = RpcReceiptError; 45 | 46 | fn method_name(&self) -> &str { 47 | "EXPERIMENTAL_receipt" 48 | } 49 | 50 | fn params(&self) -> Result { 51 | Ok(json!(self)) 52 | } 53 | } 54 | 55 | impl private::Sealed for RpcReceiptRequest {} 56 | -------------------------------------------------------------------------------- /src/methods/light_client_proof.rs: -------------------------------------------------------------------------------- 1 | //! Returns the proofs for a transaction execution. 2 | //! 3 | //! ``` 4 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 5 | //! use near_primitives::types::TransactionOrReceiptId; 6 | //! 7 | //! # #[tokio::main] 8 | //! # async fn main() -> Result<(), Box> { 9 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 10 | //! 11 | //! let request = methods::light_client_proof::RpcLightClientExecutionProofRequest { 12 | //! id: TransactionOrReceiptId::Transaction { 13 | //! transaction_hash: "47sXP4jKXCMpkUS6kcxsfNU7tqysYr5fxWFdEXQkZh6z".parse()?, 14 | //! sender_id: "aurora.pool.near".parse()?, 15 | //! }, 16 | //! light_client_head: "ANm3jm5wq1Z4rJv6tXWyiDtC3wYKpXVHY4iq6bE1te7B".parse()?, 17 | //! }; 18 | //! 19 | //! let response = client.call(request).await?; 20 | //! 21 | //! assert!(matches!( 22 | //! response, 23 | //! methods::light_client_proof::RpcLightClientExecutionProofResponse { .. } 24 | //! )); 25 | //! Ok(()) 26 | //! # } 27 | //! ``` 28 | use super::*; 29 | 30 | pub use near_jsonrpc_primitives::types::light_client::{ 31 | RpcLightClientExecutionProofRequest, RpcLightClientExecutionProofResponse, 32 | RpcLightClientProofError, 33 | }; 34 | 35 | impl RpcHandlerResponse for RpcLightClientExecutionProofResponse {} 36 | 37 | impl RpcHandlerError for RpcLightClientProofError { 38 | fn parse(value: serde_json::Value) -> Result { 39 | common::parse_unknown_block!(value => Self) 40 | } 41 | } 42 | 43 | impl RpcMethod for RpcLightClientExecutionProofRequest { 44 | type Response = RpcLightClientExecutionProofResponse; 45 | type Error = RpcLightClientProofError; 46 | 47 | fn method_name(&self) -> &str { 48 | "light_client_proof" 49 | } 50 | 51 | fn params(&self) -> Result { 52 | Ok(json!(self)) 53 | } 54 | } 55 | 56 | impl private::Sealed for RpcLightClientExecutionProofRequest {} 57 | -------------------------------------------------------------------------------- /src/methods/sandbox/patch_state.rs: -------------------------------------------------------------------------------- 1 | //! Patch account, access keys, contract code, or contract state. 2 | //! 3 | //! Only additions and mutations are supported. No deletions. 4 | //! 5 | //! Account, access keys, contract code, and contract states have different formats. See the example and docs for details about their format. 6 | //! 7 | //! ## Examples 8 | //! 9 | //! ``` 10 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 11 | //! use near_primitives::{state_record::StateRecord, account, types::AccountId, hash::CryptoHash}; 12 | //! 13 | //! # #[tokio::main] 14 | //! # async fn main() -> Result<(), Box> { 15 | //! let client = JsonRpcClient::connect("http://localhost:3030"); 16 | //! 17 | //! let request = methods::sandbox_patch_state::RpcSandboxPatchStateRequest { 18 | //! records: vec![ 19 | //! StateRecord::Account { 20 | //! account_id: "fido.testnet".parse::()?, 21 | //! account: account::Account::new(179, 0, CryptoHash::default(), 264) 22 | //! } 23 | //! ], 24 | //! }; 25 | //! 26 | //! let response = client.call(request).await?; 27 | //! 28 | //! assert!(matches!( 29 | //! response, 30 | //! methods::sandbox_patch_state::RpcSandboxPatchStateResponse { .. } 31 | //! )); 32 | //! # Ok(()) 33 | //! # } 34 | //! ``` 35 | use super::*; 36 | 37 | pub use near_jsonrpc_primitives::types::sandbox::{ 38 | RpcSandboxPatchStateError, RpcSandboxPatchStateRequest, RpcSandboxPatchStateResponse, 39 | }; 40 | 41 | impl RpcHandlerResponse for RpcSandboxPatchStateResponse {} 42 | 43 | impl RpcHandlerError for RpcSandboxPatchStateError {} 44 | 45 | impl RpcMethod for RpcSandboxPatchStateRequest { 46 | type Response = RpcSandboxPatchStateResponse; 47 | type Error = RpcSandboxPatchStateError; 48 | 49 | fn method_name(&self) -> &str { 50 | "sandbox_patch_state" 51 | } 52 | 53 | fn params(&self) -> Result { 54 | Ok(json!(self)) 55 | } 56 | } 57 | 58 | impl private::Sealed for RpcSandboxPatchStateRequest {} 59 | -------------------------------------------------------------------------------- /src/methods/experimental/protocol_config.rs: -------------------------------------------------------------------------------- 1 | //! Queries the protocol config of the blockchain at a given block. 2 | //! 3 | //! The `RpcProtocolConfigRequest` takes in a [`BlockReference`](https://docs.rs/near-primitives/0.12.0/near_primitives/types/enum.BlockReference.html) enum which has multiple variants. 4 | //! 5 | //! ## Example 6 | //! 7 | //! Returns the protocol config of the blockchain at a given block. 8 | //! 9 | //! ```no_run 10 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 11 | //! use near_primitives::types::{BlockReference, BlockId}; 12 | //! 13 | //! # #[tokio::main] 14 | //! # async fn main() -> Result<(), Box> { 15 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 16 | //! 17 | //! let request = methods::EXPERIMENTAL_protocol_config::RpcProtocolConfigRequest { 18 | //! block_reference: BlockReference::BlockId(BlockId::Height(100_000_000)) 19 | //! }; 20 | //! 21 | //! let response = client.call(request).await?; 22 | //! 23 | //! assert!(matches!( 24 | //! response, 25 | //! methods::EXPERIMENTAL_protocol_config::RpcProtocolConfigResponse { .. } 26 | //! )); 27 | //! # Ok(()) 28 | //! # } 29 | //! ``` 30 | use super::*; 31 | 32 | pub use near_jsonrpc_primitives::types::config::{ 33 | RpcProtocolConfigError, RpcProtocolConfigRequest, 34 | }; 35 | 36 | pub type RpcProtocolConfigResponse = near_chain_configs::ProtocolConfigView; 37 | 38 | impl RpcHandlerResponse for RpcProtocolConfigResponse {} 39 | 40 | impl RpcHandlerError for RpcProtocolConfigError { 41 | fn parse(value: serde_json::Value) -> Result { 42 | common::parse_unknown_block!(value => Self) 43 | } 44 | } 45 | 46 | impl RpcMethod for RpcProtocolConfigRequest { 47 | type Response = RpcProtocolConfigResponse; 48 | type Error = RpcProtocolConfigError; 49 | 50 | fn method_name(&self) -> &str { 51 | "EXPERIMENTAL_protocol_config" 52 | } 53 | 54 | fn params(&self) -> Result { 55 | Ok(json!(self)) 56 | } 57 | } 58 | 59 | impl private::Sealed for RpcProtocolConfigRequest {} 60 | -------------------------------------------------------------------------------- /src/methods/experimental/changes_in_block.rs: -------------------------------------------------------------------------------- 1 | //! Returns the changes in a block. 2 | //! 3 | //! The `RpcStateChangesInBlockRequest` takes in a [`BlockReference`](https://docs.rs/near-primitives/0.12.0/near_primitives/types/enum.BlockReference.html) enum which has multiple variants. 4 | //! 5 | //! ## Example 6 | //! 7 | //! Returns the changes in block for 8 | //! 9 | //! You can also use the `Finality` and `SyncCheckpoint` variants of [`BlockReference`](https://docs.rs/near-primitives/0.12.0/near_primitives/types/enum.BlockReference.html) to return block change details. 10 | //! 11 | //! ``` 12 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 13 | //! use near_primitives::types::{BlockReference, BlockId}; 14 | //! 15 | //! # #[tokio::main] 16 | //! # async fn main() -> Result<(), Box> { 17 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 18 | //! 19 | //! let request = methods::EXPERIMENTAL_changes_in_block::RpcStateChangesInBlockRequest { 20 | //! block_reference: BlockReference::BlockId(BlockId::Height(47988413)) 21 | //! }; 22 | //! 23 | //! let response = client.call(request).await?; 24 | //! 25 | //! assert!(matches!( 26 | //! response, 27 | //! methods::EXPERIMENTAL_changes_in_block::RpcStateChangesInBlockByTypeResponse { .. } 28 | //! )); 29 | //! # Ok(()) 30 | //! # } 31 | //! ``` 32 | use super::*; 33 | 34 | pub use near_jsonrpc_primitives::types::changes::{ 35 | RpcStateChangesError, RpcStateChangesInBlockByTypeResponse, RpcStateChangesInBlockRequest, 36 | }; 37 | 38 | impl RpcHandlerResponse for RpcStateChangesInBlockByTypeResponse {} 39 | 40 | impl RpcMethod for RpcStateChangesInBlockRequest { 41 | type Response = RpcStateChangesInBlockByTypeResponse; 42 | type Error = RpcStateChangesError; 43 | 44 | fn method_name(&self) -> &str { 45 | "EXPERIMENTAL_changes_in_block" 46 | } 47 | 48 | fn params(&self) -> Result { 49 | Ok(json!(self)) 50 | } 51 | } 52 | 53 | impl private::Sealed for RpcStateChangesInBlockRequest {} 54 | -------------------------------------------------------------------------------- /examples/auth.rs: -------------------------------------------------------------------------------- 1 | use near_jsonrpc_client::errors::{ 2 | JsonRpcError::ServerError, JsonRpcServerError::ResponseStatusError, 3 | JsonRpcServerResponseStatusError::Unauthorized, 4 | }; 5 | use near_jsonrpc_client::{auth, methods, JsonRpcClient}; 6 | use near_primitives::types::{BlockReference, Finality}; 7 | 8 | mod utils; 9 | 10 | async fn unauthorized() -> Result<(), Box> { 11 | let client = JsonRpcClient::connect("https://near-mainnet.api.pagoda.co/rpc/v1/"); 12 | 13 | let request = methods::block::RpcBlockRequest { 14 | block_reference: BlockReference::Finality(Finality::Final), 15 | }; 16 | 17 | match client.call(request).await { 18 | Ok(_) => panic!("The unauthorized request succeeded unexpectedly."), 19 | Err(ServerError(ResponseStatusError(Unauthorized))) => { 20 | eprintln!("\x1b[33mThe unauthorized request failed as expected.\x1b[0m"); 21 | } 22 | Err(error) => { 23 | eprintln!("\x1b[31mThe unauthorized request failed with an unexpected error.\x1b[0m"); 24 | eprintln!("Error: {:#?}", error); 25 | } 26 | } 27 | 28 | Ok(()) 29 | } 30 | 31 | async fn authorized(api_key: &str) -> Result<(), Box> { 32 | let client = JsonRpcClient::connect("https://near-mainnet.api.pagoda.co/rpc/v1/") 33 | .header(auth::ApiKey::new(api_key)?); 34 | 35 | let request = methods::block::RpcBlockRequest { 36 | block_reference: BlockReference::Finality(Finality::Final), 37 | }; 38 | 39 | match client.call(request).await { 40 | Ok(block) => println!("{:#?}", block), 41 | Err(error) => { 42 | eprintln!( 43 | "\x1b[31mThe authorized request failed unexpectedly, is the API key valid?\x1b[0m" 44 | ); 45 | match error { 46 | ServerError(ResponseStatusError(Unauthorized)) => { 47 | println!("Unauthorized: {}", error) 48 | } 49 | _ => println!("Unexpected error: {}", error), 50 | } 51 | } 52 | } 53 | 54 | Ok(()) 55 | } 56 | 57 | #[tokio::main] 58 | async fn main() -> Result<(), Box> { 59 | env_logger::init(); 60 | 61 | let input = utils::input("Enter an API Key: ")?; 62 | 63 | authorized(&input).await?; 64 | 65 | unauthorized().await?; 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /src/methods/validators.rs: -------------------------------------------------------------------------------- 1 | //! Queries active validators on the network. 2 | //! 3 | //! Returns details and the state of validation on the blockchain. 4 | //! 5 | //! ## Examples 6 | //! 7 | //! - Get the validators for a specified epoch. 8 | //! 9 | //! ``` 10 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 11 | //! use near_primitives::types::{EpochReference, EpochId, BlockReference, Finality}; 12 | //! 13 | //! # #[tokio::main] 14 | //! # async fn main() -> Result<(), Box> { 15 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 16 | //! 17 | //! let request = methods::validators::RpcValidatorRequest { 18 | //! epoch_reference: EpochReference::EpochId( 19 | //! "ECjBFyob3eKjDnDWDrbX4pkiSbGRAGyiHbS4ZVY8dxvD".parse()?, 20 | //! ) 21 | //! }; 22 | //! 23 | //! let response = client.call(request).await?; 24 | //! 25 | //! assert!(matches!( 26 | //! response, 27 | //! methods::validators::RpcValidatorResponse { .. } 28 | //! )); 29 | //! # Ok(()) 30 | //! # } 31 | //! ``` 32 | //! 33 | //! - Get the validators for the latest block. 34 | //! 35 | //! ``` 36 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 37 | //! use near_primitives::types::{EpochReference, EpochId, BlockId}; 38 | //! 39 | //! # #[tokio::main] 40 | //! # async fn main() -> Result<(), Box> { 41 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 42 | //! 43 | //! let request = methods::validators::RpcValidatorRequest { 44 | //! epoch_reference: EpochReference::Latest 45 | //! }; 46 | //! 47 | //! let response = client.call(request).await?; 48 | //! 49 | //! assert!(matches!( 50 | //! response, 51 | //! methods::validators::RpcValidatorResponse { .. } 52 | //! )); 53 | //! # Ok(()) 54 | //! # } 55 | //! ``` 56 | use super::*; 57 | 58 | pub use near_jsonrpc_primitives::types::validator::{RpcValidatorError, RpcValidatorRequest}; 59 | 60 | pub type RpcValidatorResponse = near_primitives::views::EpochValidatorInfo; 61 | 62 | impl RpcHandlerResponse for RpcValidatorResponse {} 63 | 64 | impl RpcMethod for RpcValidatorRequest { 65 | type Response = RpcValidatorResponse; 66 | type Error = RpcValidatorError; 67 | 68 | fn method_name(&self) -> &str { 69 | "validators" 70 | } 71 | 72 | fn params(&self) -> Result { 73 | Ok(json!(self)) 74 | } 75 | } 76 | 77 | impl private::Sealed for RpcValidatorRequest {} 78 | -------------------------------------------------------------------------------- /examples/contract_change_method_commit.rs: -------------------------------------------------------------------------------- 1 | use near_jsonrpc_client::{methods, JsonRpcClient}; 2 | use near_jsonrpc_primitives::types::query::QueryResponseKind; 3 | use near_primitives::gas::Gas; 4 | use near_primitives::transaction::{Action, FunctionCallAction, Transaction, TransactionV0}; 5 | use near_primitives::types::{Balance, BlockReference}; 6 | 7 | use serde_json::json; 8 | 9 | mod utils; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<(), Box> { 13 | env_logger::init(); 14 | 15 | let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 16 | 17 | let signer_account_id = utils::input("Enter the signer Account ID: ")?.parse()?; 18 | let signer_secret_key = utils::input("Enter the signer's private key: ")?.parse()?; 19 | 20 | let signer = near_crypto::InMemorySigner::from_secret_key(signer_account_id, signer_secret_key); 21 | 22 | let access_key_query_response = client 23 | .call(methods::query::RpcQueryRequest { 24 | block_reference: BlockReference::latest(), 25 | request: near_primitives::views::QueryRequest::ViewAccessKey { 26 | account_id: signer.get_account_id(), 27 | public_key: signer.public_key().clone(), 28 | }, 29 | }) 30 | .await?; 31 | 32 | let current_nonce = match access_key_query_response.kind { 33 | QueryResponseKind::AccessKey(access_key) => access_key.nonce, 34 | _ => Err("failed to extract current nonce")?, 35 | }; 36 | 37 | let other_account = utils::input("Enter the account to be rated: ")?; 38 | let rating = utils::input("Enter a rating: ")?.parse::()?; 39 | 40 | let transaction = TransactionV0 { 41 | signer_id: signer.get_account_id(), 42 | public_key: signer.public_key().clone(), 43 | nonce: current_nonce + 1, 44 | receiver_id: "nosedive.testnet".parse()?, 45 | block_hash: access_key_query_response.block_hash, 46 | actions: vec![Action::FunctionCall(Box::new(FunctionCallAction { 47 | method_name: "rate".to_string(), 48 | args: json!({ 49 | "account_id": other_account, 50 | "rating": rating, 51 | }) 52 | .to_string() 53 | .into_bytes(), 54 | gas: Gas::from_teragas(100), 55 | deposit: Balance::ZERO, 56 | }))], 57 | }; 58 | 59 | let request = methods::broadcast_tx_commit::RpcBroadcastTxCommitRequest { 60 | signed_transaction: Transaction::V0(transaction).sign(&signer), 61 | }; 62 | 63 | let response = client.call(request).await?; 64 | 65 | println!("response: {:#?}", response); 66 | 67 | Ok(()) 68 | } 69 | -------------------------------------------------------------------------------- /src/methods/gas_price.rs: -------------------------------------------------------------------------------- 1 | //! Returns the gas price for a specific block height or block hash. 2 | //! 3 | //! ## Examples 4 | //! 5 | //! Returns the gas fees for this block: 6 | //! 7 | //! 8 | //! - `BlockId::Height` 9 | //! 10 | //! ``` 11 | //! # use near_jsonrpc_client::{methods, JsonRpcClient}; 12 | //! use near_primitives::types::BlockId; 13 | //! 14 | //! # #[tokio::main] 15 | //! # async fn main() -> Result<(), Box> { 16 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 17 | //! 18 | //! let request = methods::gas_price::RpcGasPriceRequest { 19 | //! block_id: Some(BlockId::Height(61512623)), 20 | //! }; 21 | //! 22 | //! let response = client.call(request).await?; 23 | //! 24 | //! assert!(matches!( 25 | //! response, 26 | //! methods::gas_price::RpcGasPriceResponse { .. } 27 | //! )); 28 | //! # Ok(()) 29 | //! # } 30 | //! ``` 31 | //! 32 | //! - `BlockId::Hash` 33 | //! 34 | //! ``` 35 | //! # use near_jsonrpc_client::{methods, JsonRpcClient}; 36 | //! use near_primitives::types::BlockId; 37 | //! 38 | //! # #[tokio::main] 39 | //! # async fn main() -> Result<(), Box> { 40 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 41 | //! 42 | //! let request = methods::gas_price::RpcGasPriceRequest { 43 | //! block_id: Some(BlockId::Hash("6atGq4TUTZerVHU9qWoYfzXNBg3K4C4cca15TE6KfuBr".parse()?)), 44 | //! }; 45 | //! 46 | //! let response = client.call(request).await?; 47 | //! 48 | //! assert!(matches!( 49 | //! response, 50 | //! methods::gas_price::RpcGasPriceResponse { .. } 51 | //! )); 52 | //! # Ok(()) 53 | //! # } 54 | //! ``` 55 | use super::*; 56 | 57 | pub use near_jsonrpc_primitives::types::gas_price::{RpcGasPriceError, RpcGasPriceRequest}; 58 | 59 | pub type RpcGasPriceResponse = near_primitives::views::GasPriceView; 60 | 61 | impl RpcHandlerResponse for RpcGasPriceResponse {} 62 | 63 | impl RpcHandlerError for RpcGasPriceError { 64 | fn parse(value: serde_json::Value) -> Result { 65 | common::parse_unknown_block!(value => Self) 66 | } 67 | } 68 | 69 | impl RpcMethod for RpcGasPriceRequest { 70 | type Response = RpcGasPriceResponse; 71 | type Error = RpcGasPriceError; 72 | 73 | fn method_name(&self) -> &str { 74 | "gas_price" 75 | } 76 | 77 | fn params(&self) -> Result { 78 | Ok(json!([self.block_id])) 79 | } 80 | } 81 | 82 | impl private::Sealed for RpcGasPriceRequest {} 83 | -------------------------------------------------------------------------------- /examples/utils.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use near_jsonrpc_client::JsonRpcClient; 4 | use std::io::{self, Write}; 5 | 6 | pub fn input(query: &str) -> io::Result { 7 | print!("{}", query); 8 | io::stdout().flush()?; 9 | let mut input = String::new(); 10 | io::stdin().read_line(&mut input)?; 11 | Ok(input.trim().to_owned()) 12 | } 13 | 14 | pub fn select(print_msg: fn(), query: &str, chk: F) -> io::Result 15 | where 16 | F: Fn(&str) -> Option, 17 | { 18 | loop { 19 | print_msg(); 20 | for _ in 1..=5 { 21 | let selection = input(query)?; 22 | if let Some(selection) = chk(selection.to_lowercase().as_str()) { 23 | return Ok(selection); 24 | } 25 | println!("\x1b[31m(i)\x1b[0m invalid selection, retry.."); 26 | } 27 | } 28 | } 29 | 30 | pub fn select_network() -> io::Result { 31 | println!("========[Network Selection]========"); 32 | let network = select( 33 | || { 34 | println!(" [1] mainnet \x1b[38;5;244m(alias: m, main)\x1b[0m"); 35 | println!(" [2] testnet \x1b[38;5;244m(alias: t, test)\x1b[0m"); 36 | println!(" [3] custom \x1b[38;5;244m(alias: c)\x1b[0m"); 37 | }, 38 | "\x1b[33m(enter a selection)\x1b[0m> ", 39 | |selection| match (selection, selection.parse()) { 40 | ("m" | "main" | "mainnet", _) | (_, Ok(1)) => Some("mainnet"), 41 | ("t" | "test" | "testnet", _) | (_, Ok(2)) => Some("testnet"), 42 | ("c" | "custom", _) | (_, Ok(3)) => Some("custom"), 43 | _ => None, 44 | }, 45 | )?; 46 | let network_url = if network != "custom" { 47 | let archival = select( 48 | || (), 49 | "Should we connect to an archival node? [y/N] ", 50 | |selection| match selection { 51 | "" | "n" | "no" => Some(false), 52 | "y" | "yes" => Some(true), 53 | _ => None, 54 | }, 55 | )?; 56 | println!( 57 | "\x1b[32m(i)\x1b[0m Connected to the [{}] network{}", 58 | network, 59 | if archival { 60 | " (via an archival node)" 61 | } else { 62 | "" 63 | } 64 | ); 65 | format!( 66 | "https://{archival}rpc.{network}.near.org", 67 | archival = if archival { "archival-" } else { "" }, 68 | network = network 69 | ) 70 | } else { 71 | loop { 72 | let url = input("Enter the RPC Server Address: ")?; 73 | if let Err(err) = url.parse::() { 74 | println!("\x1b[31m(i)\x1b[0m invalid url ({}), retry..", err); 75 | continue; 76 | } 77 | break url; 78 | } 79 | }; 80 | println!("==================================="); 81 | 82 | Ok(JsonRpcClient::connect(network_url)) 83 | } 84 | 85 | fn main() { 86 | panic!("not a binary") 87 | } 88 | -------------------------------------------------------------------------------- /src/methods/experimental/tx_status.rs: -------------------------------------------------------------------------------- 1 | //! Queries the status of a transaction. 2 | //! 3 | //! ## Example 4 | //! 5 | //! Returns the final transaction result for 6 | //! 7 | //! 8 | //! ``` 9 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 10 | //! use near_primitives::views::TxExecutionStatus; 11 | //! 12 | //! # #[tokio::main] 13 | //! # async fn main() -> Result<(), Box> { 14 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 15 | //! let tx_hash = "B9aypWiMuiWR5kqzewL9eC96uZWA3qCMhLe67eBMWacq".parse()?; 16 | //! 17 | //! let request = methods::EXPERIMENTAL_tx_status::RpcTransactionStatusRequest { 18 | //! transaction_info: methods::EXPERIMENTAL_tx_status::TransactionInfo::TransactionId { 19 | //! tx_hash, 20 | //! sender_account_id: "itranscend.near".parse()?, 21 | //! }, 22 | //! wait_until: TxExecutionStatus::Executed, 23 | //! }; 24 | //! 25 | //! let response = client.call(request).await?; 26 | //! 27 | //! assert!(matches!( 28 | //! response, 29 | //! near_jsonrpc_primitives::types::transactions::RpcTransactionResponse { .. } 30 | //! )); 31 | //! # Ok(()) 32 | //! # } 33 | //! ``` 34 | use super::*; 35 | 36 | pub use near_jsonrpc_primitives::types::transactions::RpcTransactionError; 37 | pub use near_jsonrpc_primitives::types::transactions::RpcTransactionResponse; 38 | pub use near_jsonrpc_primitives::types::transactions::TransactionInfo; 39 | 40 | #[derive(Debug)] 41 | pub struct RpcTransactionStatusRequest { 42 | pub transaction_info: TransactionInfo, 43 | pub wait_until: near_primitives::views::TxExecutionStatus, 44 | } 45 | 46 | impl From 47 | for near_jsonrpc_primitives::types::transactions::RpcTransactionStatusRequest 48 | { 49 | fn from(this: RpcTransactionStatusRequest) -> Self { 50 | Self { 51 | transaction_info: this.transaction_info, 52 | wait_until: this.wait_until, 53 | } 54 | } 55 | } 56 | impl RpcMethod for RpcTransactionStatusRequest { 57 | type Response = RpcTransactionResponse; 58 | type Error = RpcTransactionError; 59 | 60 | fn method_name(&self) -> &str { 61 | "EXPERIMENTAL_tx_status" 62 | } 63 | 64 | fn params(&self) -> Result { 65 | Ok(match &self.transaction_info { 66 | TransactionInfo::Transaction { signed_tx } => { 67 | json!({ 68 | "signed_tx_base64": common::serialize_signed_transaction(signed_tx)?, 69 | "wait_until": self.wait_until 70 | }) 71 | } 72 | TransactionInfo::TransactionId { 73 | tx_hash, 74 | sender_account_id, 75 | } => { 76 | json!({ 77 | "tx_hash": tx_hash, 78 | "sender_account_id": sender_account_id, 79 | "wait_until": self.wait_until 80 | }) 81 | } 82 | }) 83 | } 84 | } 85 | 86 | impl private::Sealed for RpcTransactionStatusRequest {} 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # near-jsonrpc-client 2 | 3 | Lower-level API for interfacing with the NEAR Protocol via JSONRPC. 4 | 5 | [![Crates.io](https://img.shields.io/crates/v/near-jsonrpc-client?label=latest)](https://crates.io/crates/near-jsonrpc-client) 6 | [![Documentation](https://docs.rs/near-jsonrpc-client/badge.svg)](https://docs.rs/near-jsonrpc-client) 7 | [![MIT or Apache 2.0 Licensed](https://img.shields.io/crates/l/near-jsonrpc-client.svg)](#license) 8 | [![Dependency Status](https://deps.rs/crate/near-jsonrpc-client/0.5.1/status.svg)](https://deps.rs/crate/near-jsonrpc-client/0.5.1) 9 | 10 | ## Usage 11 | 12 | Each one of the valid JSON RPC methods are defined in the `methods` module. 13 | For instance, to make a `tx` request, you start with the `tx` module 14 | and construct a request using the `methods::tx::RpcTransactionStatusRequest` struct. 15 | 16 | ```rust 17 | use near_jsonrpc_client::{methods, JsonRpcClient}; 18 | use near_jsonrpc_primitives::types::transactions::TransactionInfo; 19 | 20 | let mainnet_client = JsonRpcClient::connect("https://archival-rpc.mainnet.near.org"); 21 | 22 | let tx_status_request = methods::tx::RpcTransactionStatusRequest { 23 | transaction_info: TransactionInfo::TransactionId { 24 | hash: "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U".parse()?, 25 | account_id: "miraclx.near".parse()?, 26 | }, 27 | }; 28 | 29 | // call a method on the server via the connected client 30 | let tx_status = mainnet_client.call(tx_status_request).await?; 31 | 32 | println!("{:?}", tx_status); 33 | ``` 34 | 35 | Check out [`the examples folder`](https://github.com/near/near-jsonrpc-client-rs/tree/master/examples) for a comprehensive list of helpful demos. You can run the examples with `cargo`. For example: `cargo run --example view_account`. 36 | 37 | 38 | ## Releasing 39 | 40 | Versioning and releasing of this crate is automated and managed by [custom fork](https://github.com/miraclx/cargo-workspaces/tree/grouping-versioning-and-exclusion) of [`cargo-workspaces`](https://github.com/pksunkara/cargo-workspaces). To publish a new version of this crate, you can do so by bumping the `version` under the `[workspace.metadata.workspaces]` section in the [package manifest](https://github.com/near/near-jsonrpc-client-rs/blob/master/Cargo.toml) and submit a PR. 41 | 42 | We have CI Infrastructure put in place to automate the process of publishing all crates once a version change has merged into master. 43 | 44 | However, before you release, make sure the [CHANGELOG](https://github.com/near/near-jsonrpc-client-rs/blob/master/CHANGELOG.md) is up to date and that the `[Unreleased]` section is present but empty. 45 | 46 | ## Contribution 47 | 48 | Unless you explicitly state otherwise, any contribution intentionally submitted 49 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 50 | dual licensed as below, without any additional terms or conditions. 51 | 52 | ## License 53 | 54 | Licensed under either of 55 | 56 | * Apache License, Version 2.0 57 | ([LICENSE-APACHE](LICENSE-APACHE) or ) 58 | * MIT license 59 | ([LICENSE-MIT](LICENSE-MIT) or ) 60 | 61 | at your option. 62 | -------------------------------------------------------------------------------- /src/methods/block.rs: -------------------------------------------------------------------------------- 1 | //! Queries data from a specific block on the network. 2 | //! 3 | //! Blocks can be referenced using either; 4 | //! - a [block ID](https://docs.near.org/docs/api/rpc#using-block_id-param) (block height or block hash) for querying historical blocks 5 | //! - or a [finality specifier](https://docs.near.org/docs/api/rpc#using-finality-param) (“final” or “optimistic”) for latest blocks. 6 | //! 7 | //! ## Examples 8 | //! 9 | //! - Query historical blocks by using a specific reference (block height or block hash). 10 | //! 11 | //! - `BlockId::Height` 12 | //! 13 | //! ``` 14 | //! # use near_jsonrpc_client::methods; 15 | //! use near_primitives::types::{BlockReference, BlockId}; 16 | //! 17 | //! let request = methods::block::RpcBlockRequest { 18 | //! block_reference: BlockReference::BlockId(BlockId::Height(83975193)) 19 | //! }; 20 | //! ``` 21 | //! 22 | //! - `BlockId::Hash` 23 | //! 24 | //! ``` 25 | //! # use near_jsonrpc_client::methods; 26 | //! # fn main() -> Result<(), Box> { 27 | //! use near_primitives::types::{BlockReference, BlockId}; 28 | //! 29 | //! let request = methods::block::RpcBlockRequest { 30 | //! block_reference: BlockReference::BlockId(BlockId::Hash( 31 | //! "G1SHrwLp55oV3kz94x3ekrR6r4ihNRWdAVZpckgBx4U4".parse()?, 32 | //! )), 33 | //! }; 34 | //! # Ok(()) 35 | //! # } 36 | //! ``` 37 | //! 38 | //! - Query latest blocks. 39 | //! 40 | //! - `Finality::Final`: Get the most recent, completely finalized block. 41 | //! 42 | //! References a block that has been validated on at least 66% of the nodes in the network. 43 | //! 44 | //! ``` 45 | //! # use near_jsonrpc_client::methods; 46 | //! use near_primitives::types::{BlockReference, Finality}; 47 | //! 48 | //! let request = methods::block::RpcBlockRequest { 49 | //! block_reference: BlockReference::Finality(Finality::Final) 50 | //! }; 51 | //! ``` 52 | //! 53 | //! - `Finality::None`: Get the most recently submitted block. 54 | //! 55 | //! Returns the latest block recorded on the node that responded to your query. 56 | //! 57 | //! ``` 58 | //! # use near_jsonrpc_client::methods; 59 | //! use near_primitives::types::{BlockReference, Finality}; 60 | //! 61 | //! let request = methods::block::RpcBlockRequest { 62 | //! block_reference: BlockReference::Finality(Finality::None) 63 | //! }; 64 | //! ``` 65 | use super::*; 66 | 67 | pub use near_jsonrpc_primitives::types::blocks::RpcBlockError; 68 | pub use near_jsonrpc_primitives::types::blocks::RpcBlockRequest; 69 | 70 | pub type RpcBlockResponse = near_primitives::views::BlockView; 71 | 72 | impl RpcHandlerResponse for RpcBlockResponse {} 73 | 74 | impl RpcHandlerError for RpcBlockError { 75 | fn parse(value: serde_json::Value) -> Result { 76 | common::parse_unknown_block!(value => Self) 77 | } 78 | } 79 | 80 | impl RpcMethod for RpcBlockRequest { 81 | type Response = RpcBlockResponse; 82 | type Error = RpcBlockError; 83 | 84 | fn method_name(&self) -> &str { 85 | "block" 86 | } 87 | 88 | fn params(&self) -> Result { 89 | Ok(json!(self)) 90 | } 91 | } 92 | 93 | impl private::Sealed for RpcBlockRequest {} 94 | -------------------------------------------------------------------------------- /src/methods/tx.rs: -------------------------------------------------------------------------------- 1 | //! Queries the status of a transaction. 2 | //! 3 | //! ## Example 4 | //! Returns the final transaction result for 5 | //! 6 | //! 7 | //! ```no_run 8 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 9 | //! 10 | //! # #[tokio::main] 11 | //! # async fn main() -> Result<(), Box> { 12 | //! use near_primitives::views::{FinalExecutionOutcomeViewEnum, TxExecutionStatus}; 13 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 14 | //! let tx_hash = "B9aypWiMuiWR5kqzewL9eC96uZWA3qCMhLe67eBMWacq".parse()?; 15 | //! 16 | //! let request = methods::tx::RpcTransactionStatusRequest { 17 | //! transaction_info: methods::tx::TransactionInfo::TransactionId { 18 | //! tx_hash, 19 | //! sender_account_id: "itranscend.near".parse()?, 20 | //! }, 21 | //! wait_until: TxExecutionStatus::Executed, 22 | //! }; 23 | //! 24 | //! let response = client.call(request).await?; 25 | //! let outcome = response.final_execution_outcome.expect("Should be executed by this moment"); 26 | //! match outcome { 27 | //! FinalExecutionOutcomeViewEnum::FinalExecutionOutcome(outcome) => { 28 | //! assert_eq!(tx_hash, outcome.transaction.hash); 29 | //! } 30 | //! FinalExecutionOutcomeViewEnum::FinalExecutionOutcomeWithReceipt(_) => { 31 | //! panic!("We haven't asked for the receipts"); 32 | //! } 33 | //! }; 34 | //! # Ok(()) 35 | //! # } 36 | //! ``` 37 | use super::*; 38 | 39 | pub use near_jsonrpc_primitives::types::transactions::RpcTransactionError; 40 | pub use near_jsonrpc_primitives::types::transactions::RpcTransactionResponse; 41 | pub use near_jsonrpc_primitives::types::transactions::TransactionInfo; 42 | 43 | #[derive(Debug)] 44 | pub struct RpcTransactionStatusRequest { 45 | pub transaction_info: TransactionInfo, 46 | pub wait_until: near_primitives::views::TxExecutionStatus, 47 | } 48 | 49 | impl From 50 | for near_jsonrpc_primitives::types::transactions::RpcTransactionStatusRequest 51 | { 52 | fn from(this: RpcTransactionStatusRequest) -> Self { 53 | Self { 54 | transaction_info: this.transaction_info, 55 | wait_until: this.wait_until, 56 | } 57 | } 58 | } 59 | 60 | impl RpcMethod for RpcTransactionStatusRequest { 61 | type Response = RpcTransactionResponse; 62 | type Error = RpcTransactionError; 63 | 64 | fn method_name(&self) -> &str { 65 | "tx" 66 | } 67 | 68 | fn params(&self) -> Result { 69 | Ok(match &self.transaction_info { 70 | TransactionInfo::Transaction { signed_tx } => { 71 | json!({ 72 | "signed_tx_base64": common::serialize_signed_transaction(signed_tx)?, 73 | "wait_until": self.wait_until 74 | }) 75 | } 76 | TransactionInfo::TransactionId { 77 | tx_hash, 78 | sender_account_id, 79 | } => { 80 | json!({ 81 | "tx_hash": tx_hash, 82 | "sender_account_id": sender_account_id, 83 | "wait_until": self.wait_until 84 | }) 85 | } 86 | }) 87 | } 88 | } 89 | 90 | impl private::Sealed for RpcTransactionStatusRequest {} 91 | -------------------------------------------------------------------------------- /src/methods/any/mod.rs: -------------------------------------------------------------------------------- 1 | //! For all intents and purposes, the predefined structures in `methods` should suffice, if you find that they 2 | //! don't or you crave extra flexibility, well, you can opt in to use the generic constructor `methods::any()` with the `any` feature flag. 3 | //! 4 | //! In this example, we retrieve only the parts from the genesis config response that we care about. 5 | //! 6 | //! ```toml 7 | //! # in Cargo.toml 8 | //! near-jsonrpc-client = { ..., features = ["any"] } 9 | //! ``` 10 | //! 11 | //! ``` 12 | //! use serde::Deserialize; 13 | //! use serde_json::json; 14 | //! 15 | //! # use near_jsonrpc_client::errors::JsonRpcError; 16 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 17 | //! use near_primitives::serialize::u128_dec_format; 18 | //! use near_primitives::types::*; 19 | //! 20 | //! #[derive(Debug, Deserialize)] 21 | //! struct PartialGenesisConfig { 22 | //! protocol_version: ProtocolVersion, 23 | //! chain_id: String, 24 | //! genesis_height: BlockHeight, 25 | //! epoch_length: BlockHeightDelta, 26 | //! #[serde(with = "u128_dec_format")] 27 | //! min_gas_price: Balance, 28 | //! #[serde(with = "u128_dec_format")] 29 | //! max_gas_price: Balance, 30 | //! #[serde(with = "u128_dec_format")] 31 | //! total_supply: Balance, 32 | //! validators: Vec, 33 | //! } 34 | //! 35 | //! impl methods::RpcHandlerResponse for PartialGenesisConfig {} 36 | //! 37 | //! # #[tokio::main] 38 | //! # async fn main() -> Result<(), JsonRpcError<()>> { 39 | //! let client = JsonRpcClient::connect("https://rpc.mainnet.near.org"); 40 | //! 41 | //! # #[cfg(feature = "any")] { 42 | //! let genesis_config_request = methods::any::>( 43 | //! "EXPERIMENTAL_genesis_config", 44 | //! json!(null), 45 | //! ); 46 | //! 47 | //! let partial_genesis = client.call(genesis_config_request).await?; 48 | //! 49 | //! println!("{:#?}", partial_genesis); 50 | //! # } 51 | //! # Ok(()) 52 | //! # } 53 | //! ``` 54 | use super::*; 55 | 56 | use std::marker::PhantomData; 57 | 58 | pub fn request( 59 | method_name: &str, 60 | params: serde_json::Value, 61 | ) -> RpcAnyRequest 62 | where 63 | T::Response: RpcHandlerResponse, 64 | T::Error: RpcHandlerError, 65 | { 66 | RpcAnyRequest { 67 | method: method_name.to_string(), 68 | params, 69 | _data: PhantomData, 70 | } 71 | } 72 | 73 | #[derive(Debug)] 74 | pub struct RpcAnyRequest { 75 | pub method: String, 76 | pub params: serde_json::Value, 77 | pub(crate) _data: PhantomData<(T, E)>, 78 | } 79 | 80 | impl private::Sealed for RpcAnyRequest {} 81 | 82 | impl RpcMethod for RpcAnyRequest 83 | where 84 | T: RpcHandlerResponse, 85 | E: RpcHandlerError, 86 | { 87 | type Response = T; 88 | type Error = E; 89 | 90 | #[inline(always)] 91 | fn method_name(&self) -> &str { 92 | &self.method 93 | } 94 | 95 | fn params(&self) -> Result { 96 | Ok(self.params.clone()) 97 | } 98 | } 99 | 100 | pub trait AnyRequestResult { 101 | type Response; 102 | type Error; 103 | } 104 | 105 | impl AnyRequestResult for Result { 106 | type Response = T; 107 | type Error = E; 108 | } 109 | 110 | impl AnyRequestResult for T { 111 | type Response = T::Response; 112 | type Error = T::Error; 113 | } 114 | -------------------------------------------------------------------------------- /src/methods/send_tx.rs: -------------------------------------------------------------------------------- 1 | //! Sends a transaction. 2 | //! 3 | //! Sends a signed transaction to the RPC, returns the guaranteed execution status and the results the blockchain can provide at the moment. 4 | //! 5 | //! Constructs a signed transaction to be sent to an RPC node. 6 | //! 7 | //! This code sample doesn't make any requests to the RPC node. It only shows how to construct the request. It's been truncated for brevity. 8 | //! 9 | //! A full example on how to use `send_tx` method can be found at [`send_tx`](https://github.com/near/near-jsonrpc-client-rs/blob/master/examples/send_tx.rs). 10 | //! 11 | //! ## Example 12 | //! 13 | //! ```no_run 14 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 15 | //! use near_primitives::gas::Gas; 16 | //! use near_jsonrpc_primitives::types::{query::QueryResponseKind, transactions::TransactionInfo}; 17 | //! use near_primitives::types::{AccountId, Balance, BlockReference}; 18 | //! use near_primitives::transaction::{Action, FunctionCallAction, Transaction, TransactionV0}; 19 | //! use serde_json::json; 20 | //! 21 | //! # #[tokio::main] 22 | //! # async fn main() -> Result<(), Box> { 23 | //! use near_primitives::views::TxExecutionStatus; 24 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 25 | //! 26 | //! let signer_account_id = "fido.testnet".parse::()?; 27 | //! let signer_secret_key = "ed25519:12dhevYshfiRqFSu8DSfxA27pTkmGRv6C5qQWTJYTcBEoB7MSTyidghi5NWXzWqrxCKgxVx97bpXPYQxYN5dieU".parse()?; 28 | //! 29 | //! let signer = near_crypto::InMemorySigner::from_secret_key(signer_account_id, signer_secret_key); 30 | //! 31 | //! let other_account = "rpc_docs.testnet".parse::()?; 32 | //! let rating = "4.5".parse::()?; 33 | //! 34 | //! let transaction = Transaction::V0(TransactionV0 { 35 | //! signer_id: signer.get_account_id(), 36 | //! public_key: signer.public_key().clone(), 37 | //! nonce: 904565 + 1, 38 | //! receiver_id: "nosedive.testnet".parse::()?, 39 | //! block_hash: "AUDcb2iNUbsmCsmYGfGuKzyXKimiNcCZjBKTVsbZGnoH".parse()?, 40 | //! actions: vec![Action::FunctionCall(Box::new(FunctionCallAction { 41 | //! method_name: "rate".to_string(), 42 | //! args: json!({ 43 | //! "account_id": other_account, 44 | //! "rating": rating, 45 | //! }) 46 | //! .to_string() 47 | //! .into_bytes(), 48 | //! gas: Gas::from_teragas(100), 49 | //! deposit: Balance::ZERO, 50 | //! }))], 51 | //! }); 52 | //! 53 | //! let request = methods::send_tx::RpcSendTransactionRequest { 54 | //! signed_transaction: transaction.sign(&signer), 55 | //! wait_until: TxExecutionStatus::IncludedFinal, 56 | //! }; 57 | //! # Ok(()) 58 | //! # } 59 | //! ``` 60 | use super::*; 61 | pub use near_jsonrpc_primitives::types::transactions::{ 62 | RpcSendTransactionRequest, RpcTransactionResponse, 63 | }; 64 | 65 | pub use near_jsonrpc_primitives::types::transactions::RpcTransactionError; 66 | pub use near_primitives::transaction::SignedTransaction; 67 | 68 | impl RpcMethod for RpcSendTransactionRequest { 69 | type Response = RpcTransactionResponse; 70 | type Error = RpcTransactionError; 71 | 72 | fn method_name(&self) -> &str { 73 | "send_tx" 74 | } 75 | 76 | fn params(&self) -> Result { 77 | Ok(json!({ 78 | "signed_tx_base64": common::serialize_signed_transaction(&self.signed_transaction)?, 79 | "wait_until": self.wait_until 80 | })) 81 | } 82 | } 83 | 84 | impl private::Sealed for RpcSendTransactionRequest {} 85 | -------------------------------------------------------------------------------- /examples/query_block.rs: -------------------------------------------------------------------------------- 1 | use near_jsonrpc_client::methods; 2 | 3 | mod utils; 4 | 5 | pub fn specify_block_reference() -> std::io::Result { 6 | println!("=========[Block Reference]========="); 7 | let block_reference = utils::select( 8 | || { 9 | println!(" [1] final \x1b[38;5;244m(alias: f, fin)\x1b[0m"); 10 | println!(" [2] optimistic \x1b[38;5;244m(alias: o, opt)\x1b[0m"); 11 | println!(" [3] block hash \x1b[38;5;244m(alias: s, hash)\x1b[0m"); 12 | println!(" [4] block height \x1b[38;5;244m(alias: h, height)\x1b[0m"); 13 | }, 14 | "\x1b[33m(enter a selection)\x1b[0m> ", 15 | |selection| match (selection, selection.parse()) { 16 | ("f" | "fin" | "final", _) | (_, Ok(1)) => { 17 | Some(near_primitives::types::BlockReference::Finality( 18 | near_primitives::types::Finality::Final, 19 | )) 20 | } 21 | ("o" | "opt" | "optimistic", _) | (_, Ok(2)) => { 22 | Some(near_primitives::types::BlockReference::Finality( 23 | near_primitives::types::Finality::None, 24 | )) 25 | } 26 | ("s" | "hash" | "block hash", _) | (_, Ok(3)) => loop { 27 | match utils::input("What block hash should we query? ") 28 | .unwrap() 29 | .parse() 30 | { 31 | Ok(block_hash) => { 32 | break Some(near_primitives::types::BlockReference::BlockId( 33 | near_primitives::types::BlockId::Hash(block_hash), 34 | )) 35 | } 36 | _ => println!("(i) Invalid block hash, please reenter!"), 37 | } 38 | }, 39 | ("h" | "height" | "block height", _) | (_, Ok(4)) => loop { 40 | match utils::input("What block height should we query? ") 41 | .unwrap() 42 | .parse() 43 | { 44 | Ok(block_height) => { 45 | break Some(near_primitives::types::BlockReference::BlockId( 46 | near_primitives::types::BlockId::Height(block_height), 47 | )) 48 | } 49 | _ => println!("(i) Invalid block height, please reenter!"), 50 | } 51 | }, 52 | _ => None, 53 | }, 54 | )?; 55 | println!("==================================="); 56 | 57 | Ok(block_reference) 58 | } 59 | 60 | #[tokio::main] 61 | async fn main() -> Result<(), Box> { 62 | env_logger::init(); 63 | 64 | let client = utils::select_network()?; 65 | 66 | // tolerate only 3 retries 67 | for _ in 1..=3 { 68 | let block_reference = specify_block_reference()?; 69 | 70 | match client 71 | .call(methods::block::RpcBlockRequest { block_reference }) 72 | .await 73 | { 74 | Ok(block_details) => println!("{:#?}", block_details), 75 | Err(err) => match err.handler_error() { 76 | Some(methods::block::RpcBlockError::UnknownBlock { .. }) => { 77 | println!("(i) Unknown block!"); 78 | continue; 79 | } 80 | Some(err) => { 81 | println!("(i) An error occurred `{:#?}`", err); 82 | continue; 83 | } 84 | _ => println!("(i) A non-handler error ocurred `{:#?}`", err), 85 | }, 86 | }; 87 | break; 88 | } 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /src/methods/broadcast_tx_commit.rs: -------------------------------------------------------------------------------- 1 | //! Sends blocking transactions. 2 | //! 3 | //! Sends a signed transaction to the RPC and waits until the transaction is fully complete. 4 | //! 5 | //! Constructs a signed transaction to be sent to an RPC node. 6 | //! 7 | //! This code sample doesn't make any requests to the RPC node. It only shows how to construct the request. It's been truncated for brevity. 8 | //! 9 | //! A full example on how to use `broadcast_tx_commit` method can be found at [`contract_change_method`](https://github.com/near/near-jsonrpc-client-rs/blob/master/examples/contract_change_method_commit.rs). 10 | //! 11 | //! ## Example 12 | //! 13 | //! ```no_run 14 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 15 | //! use near_jsonrpc_primitives::types::{query::QueryResponseKind, transactions::TransactionInfo}; 16 | //! use near_primitives::gas::Gas; 17 | //! use near_primitives::types::{AccountId, Balance, BlockReference}; 18 | //! use near_primitives::transaction::{Action, FunctionCallAction, Transaction, TransactionV0}; 19 | //! use serde_json::json; 20 | //! 21 | //! # #[tokio::main] 22 | //! # async fn main() -> Result<(), Box> { 23 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 24 | //! 25 | //! let signer_account_id = "fido.testnet".parse::()?; 26 | //! let signer_secret_key = "ed25519:12dhevYshfiRqFSu8DSfxA27pTkmGRv6C5qQWTJYTcBEoB7MSTyidghi5NWXzWqrxCKgxVx97bpXPYQxYN5dieU".parse()?; 27 | //! 28 | //! let signer = near_crypto::InMemorySigner::from_secret_key(signer_account_id, signer_secret_key); 29 | //! 30 | //! let other_account = "rpc_docs.testnet".parse::()?; 31 | //! let rating = "4.5".parse::()?; 32 | //! 33 | //! let transaction = Transaction::V0(TransactionV0 { 34 | //! signer_id: signer.get_account_id(), 35 | //! public_key: signer.public_key().clone(), 36 | //! nonce: 904565 + 1, 37 | //! receiver_id: "nosedive.testnet".parse::()?, 38 | //! block_hash: "AUDcb2iNUbsmCsmYGfGuKzyXKimiNcCZjBKTVsbZGnoH".parse()?, 39 | //! actions: vec![Action::FunctionCall(Box::new(FunctionCallAction { 40 | //! method_name: "rate".to_string(), 41 | //! args: json!({ 42 | //! "account_id": other_account, 43 | //! "rating": rating, 44 | //! }) 45 | //! .to_string() 46 | //! .into_bytes(), 47 | //! gas: Gas::from_teragas(100), 48 | //! deposit: Balance::ZERO, 49 | //! }))], 50 | //! }); 51 | //! 52 | //! let request = methods::broadcast_tx_commit::RpcBroadcastTxCommitRequest { 53 | //! signed_transaction: transaction.sign(&signer) 54 | //! }; 55 | //! # Ok(()) 56 | //! # } 57 | //! ``` 58 | use super::*; 59 | 60 | pub use near_jsonrpc_primitives::types::transactions::RpcTransactionError; 61 | pub use near_primitives::transaction::SignedTransaction; 62 | 63 | pub type RpcBroadcastTxCommitResponse = near_primitives::views::FinalExecutionOutcomeView; 64 | 65 | #[derive(Debug)] 66 | pub struct RpcBroadcastTxCommitRequest { 67 | pub signed_transaction: SignedTransaction, 68 | } 69 | 70 | impl From 71 | for near_jsonrpc_primitives::types::transactions::RpcSendTransactionRequest 72 | { 73 | fn from(this: RpcBroadcastTxCommitRequest) -> Self { 74 | Self { 75 | signed_transaction: this.signed_transaction, 76 | wait_until: near_primitives::views::TxExecutionStatus::default(), 77 | } 78 | } 79 | } 80 | 81 | impl RpcMethod for RpcBroadcastTxCommitRequest { 82 | type Response = RpcBroadcastTxCommitResponse; 83 | type Error = RpcTransactionError; 84 | 85 | fn method_name(&self) -> &str { 86 | "broadcast_tx_commit" 87 | } 88 | 89 | fn params(&self) -> Result { 90 | Ok(json!([common::serialize_signed_transaction( 91 | &self.signed_transaction 92 | )?])) 93 | } 94 | } 95 | 96 | impl private::Sealed for RpcBroadcastTxCommitRequest {} 97 | -------------------------------------------------------------------------------- /src/methods/broadcast_tx_async.rs: -------------------------------------------------------------------------------- 1 | //! Sends asynchronous transactions. 2 | //! 3 | //! ## Example 4 | //! 5 | //! Constructs a signed transaction to be sent to an RPC node. It returns the transaction hash if successful. 6 | //! 7 | //! This code sample doesn't make any requests to the RPC node. It only shows how to construct the request. It's been truncated for brevity sake. 8 | //! 9 | //! A full example on how to use `broadcast_tx_async` method can be found at [`contract_change_method`](https://github.com/near/near-jsonrpc-client-rs/blob/master/examples/contract_change_method.rs). 10 | //! 11 | //! ```no_run 12 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 13 | //! use near_primitives::gas::Gas; 14 | //! use near_primitives::types::{AccountId, Balance}; 15 | //! use near_primitives::transaction::{Action, FunctionCallAction, Transaction, TransactionV0}; 16 | //! use near_crypto::SecretKey; 17 | //! use core::str::FromStr; 18 | //! use serde_json::json; 19 | //! 20 | //! # #[tokio::main] 21 | //! # async fn main() -> Result<(), Box> { 22 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 23 | //! 24 | //! let signer_account_id = "fido.testnet".parse::()?; 25 | //! let signer_secret_key = SecretKey::from_str("ed25519:12dhevYshfiRqFSu8DSfxA27pTkmGRv6C5qQWTJYTcBEoB7MSTyidghi5NWXzWqrxCKgxVx97bpXPYQxYN5dieU")?; 26 | //! 27 | //! let signer = near_crypto::InMemorySigner::from_secret_key(signer_account_id, signer_secret_key); 28 | //! 29 | //! let other_account = "rpc_docs.testnet".parse::()?; 30 | //! let rating = "4.5".parse::()?; 31 | //! 32 | //! let transaction = Transaction::V0(TransactionV0 { 33 | //! signer_id: signer.get_account_id(), 34 | //! public_key: signer.public_key().clone(), 35 | //! nonce: 10223934 + 1, 36 | //! receiver_id: "nosedive.testnet".parse::()?, 37 | //! block_hash: "AUDcb2iNUbsmCsmYGfGuKzyXKimiNcCZjBKTVsbZGnoH".parse()?, 38 | //! actions: vec![Action::FunctionCall(Box::new(FunctionCallAction { 39 | //! method_name: "rate".to_string(), 40 | //! args: json!({ 41 | //! "account_id": other_account, 42 | //! "rating": rating, 43 | //! }) 44 | //! .to_string() 45 | //! .into_bytes(), 46 | //! gas: Gas::from_teragas(100), 47 | //! deposit: Balance::ZERO, 48 | //! }))], 49 | //! }); 50 | //! 51 | //! let request = methods::broadcast_tx_async::RpcBroadcastTxAsyncRequest { 52 | //! signed_transaction: transaction.sign(&signer) 53 | //! }; 54 | //! # Ok(()) 55 | //! # } 56 | //! ``` 57 | use super::*; 58 | 59 | pub use near_primitives::transaction::SignedTransaction; 60 | 61 | pub type RpcBroadcastTxAsyncResponse = near_primitives::hash::CryptoHash; 62 | 63 | #[derive(Debug)] 64 | pub struct RpcBroadcastTxAsyncRequest { 65 | pub signed_transaction: SignedTransaction, 66 | } 67 | 68 | impl From 69 | for near_jsonrpc_primitives::types::transactions::RpcSendTransactionRequest 70 | { 71 | fn from(this: RpcBroadcastTxAsyncRequest) -> Self { 72 | Self { 73 | signed_transaction: this.signed_transaction, 74 | wait_until: near_primitives::views::TxExecutionStatus::None, 75 | } 76 | } 77 | } 78 | 79 | #[derive(Debug, Serialize, Deserialize, Error)] 80 | #[error("{}", unreachable!("fatal: this error should never be constructed"))] 81 | pub enum RpcBroadcastTxAsyncError {} 82 | 83 | impl RpcHandlerResponse for RpcBroadcastTxAsyncResponse {} 84 | 85 | impl RpcHandlerError for RpcBroadcastTxAsyncError {} 86 | 87 | impl RpcMethod for RpcBroadcastTxAsyncRequest { 88 | type Response = RpcBroadcastTxAsyncResponse; 89 | type Error = RpcBroadcastTxAsyncError; 90 | 91 | fn method_name(&self) -> &str { 92 | "broadcast_tx_async" 93 | } 94 | 95 | fn params(&self) -> Result { 96 | Ok(json!([common::serialize_signed_transaction( 97 | &self.signed_transaction 98 | )?])) 99 | } 100 | } 101 | 102 | impl private::Sealed for RpcBroadcastTxAsyncRequest {} 103 | -------------------------------------------------------------------------------- /examples/contract_change_method.rs: -------------------------------------------------------------------------------- 1 | use near_jsonrpc_client::{methods, JsonRpcClient}; 2 | use near_jsonrpc_primitives::types::query::QueryResponseKind; 3 | use near_jsonrpc_primitives::types::transactions::{RpcTransactionError, TransactionInfo}; 4 | use near_primitives::gas::Gas; 5 | use near_primitives::transaction::{Action, FunctionCallAction, Transaction, TransactionV0}; 6 | use near_primitives::types::{Balance, BlockReference}; 7 | use near_primitives::views::TxExecutionStatus; 8 | 9 | use serde_json::json; 10 | use tokio::time; 11 | 12 | mod utils; 13 | 14 | #[tokio::main] 15 | async fn main() -> Result<(), Box> { 16 | env_logger::init(); 17 | 18 | let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 19 | 20 | let signer_account_id = utils::input("Enter the signer Account ID: ")?.parse()?; 21 | let signer_secret_key = utils::input("Enter the signer's private key: ")?.parse()?; 22 | 23 | let signer = near_crypto::InMemorySigner::from_secret_key(signer_account_id, signer_secret_key); 24 | 25 | let access_key_query_response = client 26 | .call(methods::query::RpcQueryRequest { 27 | block_reference: BlockReference::latest(), 28 | request: near_primitives::views::QueryRequest::ViewAccessKey { 29 | account_id: signer.get_account_id(), 30 | public_key: signer.public_key().clone(), 31 | }, 32 | }) 33 | .await?; 34 | 35 | let current_nonce = match access_key_query_response.kind { 36 | QueryResponseKind::AccessKey(access_key) => access_key.nonce, 37 | _ => Err("failed to extract current nonce")?, 38 | }; 39 | 40 | let other_account = utils::input("Enter the account to be rated: ")?; 41 | let rating = utils::input("Enter a rating: ")?.parse::()?; 42 | 43 | let transaction = TransactionV0 { 44 | signer_id: signer.get_account_id(), 45 | public_key: signer.public_key().clone(), 46 | nonce: current_nonce + 1, 47 | receiver_id: "nosedive.testnet".parse()?, 48 | block_hash: access_key_query_response.block_hash, 49 | actions: vec![Action::FunctionCall(Box::new(FunctionCallAction { 50 | method_name: "rate".to_string(), 51 | args: json!({ 52 | "account_id": other_account, 53 | "rating": rating, 54 | }) 55 | .to_string() 56 | .into_bytes(), 57 | gas: Gas::from_teragas(100), 58 | deposit: Balance::ZERO, 59 | }))], 60 | }; 61 | 62 | let request = methods::broadcast_tx_async::RpcBroadcastTxAsyncRequest { 63 | signed_transaction: Transaction::V0(transaction).sign(&signer), 64 | }; 65 | 66 | let sent_at = time::Instant::now(); 67 | let tx_hash = client.call(request).await?; 68 | 69 | loop { 70 | let response = client 71 | .call(methods::tx::RpcTransactionStatusRequest { 72 | transaction_info: TransactionInfo::TransactionId { 73 | tx_hash, 74 | sender_account_id: signer.get_account_id(), 75 | }, 76 | wait_until: TxExecutionStatus::Executed, 77 | }) 78 | .await; 79 | let received_at = time::Instant::now(); 80 | let delta = (received_at - sent_at).as_secs(); 81 | 82 | if delta > 60 { 83 | Err("time limit exceeded for the transaction to be recognized")?; 84 | } 85 | 86 | match response { 87 | Err(err) => match err.handler_error() { 88 | Some( 89 | RpcTransactionError::TimeoutError 90 | | RpcTransactionError::UnknownTransaction { .. }, 91 | ) => { 92 | time::sleep(time::Duration::from_secs(2)).await; 93 | continue; 94 | } 95 | _ => Err(err)?, 96 | }, 97 | Ok(response) => { 98 | println!("response gotten after: {}s", delta); 99 | println!("response: {:#?}", response); 100 | break; 101 | } 102 | } 103 | } 104 | 105 | Ok(()) 106 | } 107 | -------------------------------------------------------------------------------- /src/methods/chunk.rs: -------------------------------------------------------------------------------- 1 | //! Returns details of a specific chunk. 2 | //! 3 | //! You can use the [`block`](crate::methods::block) RPC method to get a valid chunk hash. 4 | //! 5 | //! ## Examples 6 | //! 7 | //! Chunks can be queried using one of two `ChunkReference` variants: `BlockShardId` or `ChunkHash`. 8 | //! 9 | //! - `BlockShardId`: Query a chunk by specifying its block ID (block height or block hash) and shard ID. 10 | //! 11 | //! - `BlockId::Hash` 12 | //! 13 | //! ``` 14 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 15 | //! use near_jsonrpc_primitives::types::chunks; 16 | //! use near_primitives::types::{BlockId, ShardId}; 17 | //! 18 | //! # #[tokio::main] 19 | //! # async fn main() -> Result<(), Box> { 20 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 21 | //! 22 | //! let request = methods::chunk::RpcChunkRequest { 23 | //! chunk_reference: chunks::ChunkReference::BlockShardId { 24 | //! block_id: BlockId::Hash("6atGq4TUTZerVHU9qWoYfzXNBg3K4C4cca15TE6KfuBr".parse()?), 25 | //! shard_id: ShardId::from(0), 26 | //! } 27 | //! }; 28 | //! 29 | //! let response = client.call(request).await?; 30 | //! 31 | //! assert!(matches!( 32 | //! response, 33 | //! methods::chunk::RpcChunkResponse { .. } 34 | //! )); 35 | //! # Ok(()) 36 | //! # } 37 | //! ``` 38 | //! 39 | //! - `BlockId::Height` 40 | //! 41 | //! ``` 42 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 43 | //! use near_jsonrpc_primitives::types::chunks; 44 | //! use near_primitives::types::{BlockId, ShardId}; 45 | //! 46 | //! # #[tokio::main] 47 | //! # async fn main() -> Result<(), Box> { 48 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 49 | //! 50 | //! let request = methods::chunk::RpcChunkRequest { 51 | //! chunk_reference: chunks::ChunkReference::BlockShardId { 52 | //! block_id: BlockId::Height(61512623), 53 | //! shard_id: ShardId::from(3), 54 | //! } 55 | //! }; 56 | //! 57 | //! let response = client.call(request).await?; 58 | //! 59 | //! assert!(matches!( 60 | //! response, 61 | //! methods::chunk::RpcChunkResponse { .. } 62 | //! )); 63 | //! # Ok(()) 64 | //! # } 65 | //! ``` 66 | //! 67 | //! 68 | //! - `ChunkHash`: Query a chunk by a specific reference via it's associated chunk hash. 69 | //! 70 | //! ``` 71 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 72 | //! use near_jsonrpc_primitives::types::chunks; 73 | //! 74 | //! # #[tokio::main] 75 | //! # async fn main() -> Result<(), Box> { 76 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 77 | //! 78 | //! let request = methods::chunk::RpcChunkRequest{ 79 | //! chunk_reference: chunks::ChunkReference::ChunkHash { 80 | //! chunk_id: "6GTgCQ5genLEEiPspEvdZEJooBzgWRrUnur9eGSdeTTD".parse()?, 81 | //! } 82 | //! }; 83 | //! 84 | //! let response = client.call(request).await?; 85 | //! 86 | //! assert!(matches!( 87 | //! response, 88 | //! methods::chunk::RpcChunkResponse { .. } 89 | //! )); 90 | //! # Ok(()) 91 | //! # } 92 | //! ``` 93 | use super::*; 94 | 95 | pub use near_jsonrpc_primitives::types::chunks::{ChunkReference, RpcChunkError, RpcChunkRequest}; 96 | 97 | pub type RpcChunkResponse = near_primitives::views::ChunkView; 98 | 99 | impl RpcHandlerResponse for RpcChunkResponse {} 100 | 101 | impl RpcHandlerError for RpcChunkError { 102 | fn parse(value: serde_json::Value) -> Result { 103 | common::parse_unknown_block!(value => Self) 104 | } 105 | } 106 | 107 | impl RpcMethod for RpcChunkRequest { 108 | type Response = RpcChunkResponse; 109 | type Error = RpcChunkError; 110 | 111 | fn method_name(&self) -> &str { 112 | "chunk" 113 | } 114 | 115 | fn params(&self) -> Result { 116 | Ok(json!(self)) 117 | } 118 | } 119 | 120 | impl private::Sealed for RpcChunkRequest {} 121 | -------------------------------------------------------------------------------- /examples/send_tx.rs: -------------------------------------------------------------------------------- 1 | use near_jsonrpc_client::{methods, JsonRpcClient}; 2 | use near_jsonrpc_primitives::types::query::QueryResponseKind; 3 | use near_jsonrpc_primitives::types::transactions::{RpcTransactionError, TransactionInfo}; 4 | use near_primitives::gas::Gas; 5 | use near_primitives::transaction::{Action, FunctionCallAction, Transaction, TransactionV0}; 6 | use near_primitives::types::{Balance, BlockReference}; 7 | use near_primitives::views::TxExecutionStatus; 8 | use tokio::time; 9 | 10 | mod utils; 11 | 12 | #[tokio::main] 13 | async fn main() -> Result<(), Box> { 14 | env_logger::init(); 15 | 16 | let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 17 | 18 | let signer_account_id = utils::input("Enter the signer Account ID: ")?.parse()?; 19 | let signer_secret_key = utils::input("Enter the signer's private key: ")?.parse()?; 20 | let wait_until_str = utils::input("Enter the desired guaranteed execution status (can be one of: NONE, INCLUDED, INCLUDED_FINAL, EXECUTED, FINAL): ")?; 21 | let wait_until: TxExecutionStatus = serde_json::from_value(serde_json::json!(wait_until_str))?; 22 | 23 | let signer = near_crypto::InMemorySigner::from_secret_key(signer_account_id, signer_secret_key); 24 | 25 | let access_key_query_response = client 26 | .call(methods::query::RpcQueryRequest { 27 | block_reference: BlockReference::latest(), 28 | request: near_primitives::views::QueryRequest::ViewAccessKey { 29 | account_id: signer.get_account_id(), 30 | public_key: signer.public_key().clone(), 31 | }, 32 | }) 33 | .await?; 34 | 35 | let current_nonce = match access_key_query_response.kind { 36 | QueryResponseKind::AccessKey(access_key) => access_key.nonce, 37 | _ => Err("failed to extract current nonce")?, 38 | }; 39 | 40 | let other_account = utils::input("Enter the account to be rated: ")?; 41 | let rating = utils::input("Enter a rating: ")?.parse::()?; 42 | 43 | let transaction = Transaction::V0(TransactionV0 { 44 | signer_id: signer.get_account_id(), 45 | public_key: signer.public_key(), 46 | nonce: current_nonce + 1, 47 | receiver_id: "nosedive.testnet".parse()?, 48 | block_hash: access_key_query_response.block_hash, 49 | actions: vec![Action::FunctionCall(Box::new(FunctionCallAction { 50 | method_name: "rate".to_string(), 51 | args: serde_json::json!({ 52 | "account_id": other_account, 53 | "rating": rating, 54 | }) 55 | .to_string() 56 | .into_bytes(), 57 | gas: Gas::from_teragas(100), 58 | deposit: Balance::ZERO, 59 | }))], 60 | }); 61 | let tx_hash = transaction.get_hash_and_size().0; 62 | 63 | let request = methods::send_tx::RpcSendTransactionRequest { 64 | signed_transaction: transaction.sign(&signer), 65 | wait_until: wait_until.clone(), 66 | }; 67 | 68 | let sent_at = time::Instant::now(); 69 | let response = match client.call(request).await { 70 | Ok(response) => response, 71 | Err(err) => { 72 | match err.handler_error() { 73 | Some(RpcTransactionError::TimeoutError) => {} 74 | _ => Err(err)?, 75 | } 76 | loop { 77 | let response = client 78 | .call(methods::tx::RpcTransactionStatusRequest { 79 | transaction_info: TransactionInfo::TransactionId { 80 | tx_hash, 81 | sender_account_id: signer.get_account_id(), 82 | }, 83 | wait_until: wait_until.clone(), 84 | }) 85 | .await; 86 | let received_at = time::Instant::now(); 87 | let delta = (received_at - sent_at).as_secs(); 88 | 89 | if delta > 60 { 90 | Err("time limit exceeded for the transaction to be recognized")?; 91 | } 92 | 93 | match response { 94 | Err(err) => match err.handler_error() { 95 | Some(RpcTransactionError::TimeoutError) => {} 96 | _ => Err(err)?, 97 | }, 98 | Ok(response) => { 99 | break response; 100 | } 101 | } 102 | } 103 | } 104 | }; 105 | 106 | let received_at = time::Instant::now(); 107 | let delta = (received_at - sent_at).as_secs(); 108 | println!("response gotten after: {}s", delta); 109 | println!("response: {:#?}", response); 110 | 111 | Ok(()) 112 | } 113 | -------------------------------------------------------------------------------- /examples/query_tx.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use near_jsonrpc_client::methods; 4 | use near_primitives::{hash::CryptoHash, types::AccountId}; 5 | 6 | mod utils; 7 | 8 | pub fn specify_block_reference() -> std::io::Result { 9 | println!("=========[Block Reference]========="); 10 | let block_reference = utils::select( 11 | || { 12 | println!(" [1] final \x1b[38;5;244m(alias: f, fin)\x1b[0m"); 13 | println!(" [2] optimistic \x1b[38;5;244m(alias: o, opt)\x1b[0m"); 14 | println!(" [3] block hash \x1b[38;5;244m(alias: s, hash)\x1b[0m"); 15 | println!(" [4] block height \x1b[38;5;244m(alias: h, height)\x1b[0m"); 16 | }, 17 | "\x1b[33m(enter a selection)\x1b[0m> ", 18 | |selection| match (selection, selection.parse()) { 19 | ("f" | "fin" | "final", _) | (_, Ok(1)) => { 20 | Some(near_primitives::types::BlockReference::Finality( 21 | near_primitives::types::Finality::Final, 22 | )) 23 | } 24 | ("o" | "opt" | "optimistic", _) | (_, Ok(2)) => { 25 | Some(near_primitives::types::BlockReference::Finality( 26 | near_primitives::types::Finality::None, 27 | )) 28 | } 29 | ("s" | "hash" | "block hash", _) | (_, Ok(3)) => loop { 30 | match utils::input("What block hash should we query? ") 31 | .unwrap() 32 | .parse() 33 | { 34 | Ok(block_hash) => { 35 | break Some(near_primitives::types::BlockReference::BlockId( 36 | near_primitives::types::BlockId::Hash(block_hash), 37 | )) 38 | } 39 | _ => println!("(i) Invalid block hash, please reenter!"), 40 | } 41 | }, 42 | ("h" | "height" | "block height", _) | (_, Ok(4)) => loop { 43 | match utils::input("What block height should we query? ") 44 | .unwrap() 45 | .parse() 46 | { 47 | Ok(block_height) => { 48 | break Some(near_primitives::types::BlockReference::BlockId( 49 | near_primitives::types::BlockId::Height(block_height), 50 | )) 51 | } 52 | _ => println!("(i) Invalid block height, please reenter!"), 53 | } 54 | }, 55 | _ => None, 56 | }, 57 | )?; 58 | println!("==================================="); 59 | 60 | Ok(block_reference) 61 | } 62 | 63 | fn get_valid_input( 64 | prompt: &str, 65 | max_retries: usize, 66 | ) -> Result> { 67 | for _ in 0..max_retries { 68 | let input = utils::input(prompt)?; 69 | if let Ok(value) = input.parse() { 70 | return Ok(value); 71 | } else { 72 | println!("(i) Invalid input!"); 73 | } 74 | } 75 | 76 | Err(format!("(i) Maximum number of retries ({}) reached", max_retries).into()) 77 | } 78 | 79 | #[tokio::main] 80 | async fn main() -> Result<(), Box> { 81 | env_logger::init(); 82 | 83 | let client = utils::select_network()?; 84 | 85 | // tolerate only 3 retries for a non-failing transaction hash 86 | for _ in 1..=3 { 87 | let tx_hash: CryptoHash = get_valid_input("What transaction hash should we query", 3)?; 88 | let account_id: AccountId = get_valid_input("What account signed this transaction", 3)?; 89 | let wait_until_str = utils::input("Enter the desired guaranteed execution status (can be one of: NONE, INCLUDED, INCLUDED_FINAL, EXECUTED, FINAL): ")?; 90 | let wait_until = serde_json::from_value(serde_json::json!(wait_until_str))?; 91 | 92 | match client 93 | .call(methods::tx::RpcTransactionStatusRequest { 94 | transaction_info: methods::tx::TransactionInfo::TransactionId { 95 | tx_hash, 96 | sender_account_id: account_id, 97 | }, 98 | wait_until, 99 | }) 100 | .await 101 | { 102 | Ok(tx_details) => println!("{:#?}", tx_details), 103 | Err(err) => match err.handler_error() { 104 | Some(err) => { 105 | println!("(i) An error occurred `{:#?}`", err); 106 | continue; 107 | } 108 | _ => println!("(i) A non-handler error occurred `{:#?}`", err), 109 | }, 110 | }; 111 | break; 112 | } 113 | 114 | Ok(()) 115 | } 116 | -------------------------------------------------------------------------------- /src/header.rs: -------------------------------------------------------------------------------- 1 | //! Client headers. 2 | //! 3 | //! This module includes everything you need to build valid header entries. 4 | 5 | use std::marker::PhantomData; 6 | 7 | pub use reqwest::header::{HeaderName, HeaderValue, InvalidHeaderValue, ToStrError}; 8 | 9 | /// [`HeaderEntry`] attribute identifying those that have been prevalidated. 10 | /// 11 | /// The specification of a header entry identified by this discriminant doesn't return a [`Result`]. 12 | /// 13 | /// ### Example 14 | /// 15 | /// This example adds the header name `custom-header` and value `custom:some-value`. 16 | /// 17 | /// ``` 18 | /// use near_jsonrpc_client::{ 19 | /// header::{HeaderEntry, HeaderValue, Prevalidated}, 20 | /// methods, JsonRpcClient, 21 | /// }; 22 | /// 23 | /// struct CustomHeader(HeaderValue); 24 | /// 25 | /// impl HeaderEntry for CustomHeader { 26 | /// type HeaderName = &'static str; 27 | /// type HeaderValue = HeaderValue; 28 | /// 29 | /// fn header_name(&self) -> &Self::HeaderName { 30 | /// &"custom-header" 31 | /// } 32 | /// 33 | /// fn header_pair(self) -> (Self::HeaderName, Self::HeaderValue) { 34 | /// ("custom-header", self.0) 35 | /// } 36 | /// } 37 | /// # #[tokio::main] 38 | /// # async fn main() -> Result<(), Box> { 39 | /// 40 | /// let header_value = HeaderValue::try_from("custom:some-value")?; // <- error handling here 41 | /// 42 | /// let client = JsonRpcClient::connect("https://rpc.testnet.near.org").header(CustomHeader(header_value)); 43 | /// # Ok(()) 44 | /// # } 45 | pub struct Prevalidated { 46 | _priv: (), 47 | } 48 | 49 | /// [`HeaderEntry`] attribute identifying those that need to be validated. 50 | /// 51 | /// The specification of a header entry identified by this discriminant will return a [`Result`]. 52 | /// 53 | /// ### Example 54 | /// 55 | /// This example adds the header name `custom-header` and value `custom:some-value`. 56 | /// 57 | /// ``` 58 | /// # use std::{fmt, error::Error}; 59 | /// use near_jsonrpc_client::{ 60 | /// header::{HeaderEntry, HeaderValue, Postvalidated}, 61 | /// methods, JsonRpcClient, 62 | /// }; 63 | /// 64 | /// struct CustomValue(&'static str); 65 | /// 66 | /// struct CustomHeader(CustomValue); 67 | /// 68 | /// # #[derive(Debug)] 69 | /// struct CustomError; 70 | /// # impl Error for CustomError {} 71 | /// # impl fmt::Display for CustomError { 72 | /// # fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 73 | /// # write!(f, "custom error") 74 | /// # } 75 | /// # } 76 | /// 77 | /// impl TryFrom for HeaderValue { 78 | /// type Error = CustomError; 79 | /// 80 | /// fn try_from(v: CustomValue) -> Result { 81 | /// HeaderValue::try_from(format!("custom:{}", v.0)).map_err(|_| CustomError) 82 | /// } 83 | /// } 84 | /// 85 | /// impl HeaderEntry> for CustomHeader { 86 | /// type HeaderName = &'static str; 87 | /// type HeaderValue = CustomValue; 88 | /// 89 | /// fn header_name(&self) -> &Self::HeaderName { 90 | /// &"custom-header" 91 | /// } 92 | /// 93 | /// fn header_pair(self) -> (Self::HeaderName, Self::HeaderValue) { 94 | /// ("custom-header", self.0) 95 | /// } 96 | /// } 97 | /// # #[tokio::main] 98 | /// # async fn main() -> Result<(), Box> { 99 | /// 100 | /// let client = JsonRpcClient::connect("https://rpc.testnet.near.org") 101 | /// .header(CustomHeader(CustomValue("some-value")))?; // <- error handling here 102 | /// # Ok(()) 103 | /// # } 104 | pub struct Postvalidated { 105 | _priv: PhantomData, 106 | } 107 | 108 | /// Trait for identifying valid header entries. 109 | /// 110 | /// Header entries are distinguished by their discrimimants, (See [HeaderEntryDiscriminant]). 111 | pub trait HeaderEntry: Sized 112 | where 113 | D: HeaderEntryDiscriminant, 114 | { 115 | type HeaderName; 116 | type HeaderValue; 117 | 118 | fn header_name(&self) -> &Self::HeaderName; 119 | fn header_pair(self) -> (Self::HeaderName, Self::HeaderValue); 120 | } 121 | 122 | pub use discriminant::HeaderEntryDiscriminant; 123 | mod discriminant { 124 | use reqwest::header::IntoHeaderName; 125 | 126 | use super::{super::JsonRpcClient, HeaderEntry, HeaderValue, Postvalidated, Prevalidated}; 127 | 128 | pub trait Sealed {} 129 | 130 | /// Trait for defining a [`HeaderEntry`]'s application on a client. 131 | pub trait HeaderEntryDiscriminant: Sealed { 132 | type Output; 133 | 134 | fn apply(client: JsonRpcClient, entry: H) -> Self::Output; 135 | } 136 | 137 | impl Sealed for Prevalidated {} 138 | impl HeaderEntryDiscriminant for Prevalidated 139 | where 140 | T: HeaderEntry, 141 | T::HeaderName: IntoHeaderName, 142 | { 143 | type Output = JsonRpcClient; 144 | 145 | fn apply(mut client: JsonRpcClient, entry: T) -> Self::Output { 146 | let (k, v) = entry.header_pair(); 147 | client.headers.insert(k, v); 148 | client 149 | } 150 | } 151 | 152 | impl Sealed for Postvalidated {} 153 | impl HeaderEntryDiscriminant for Postvalidated 154 | where 155 | T: HeaderEntry, 156 | T::HeaderName: IntoHeaderName, 157 | T::HeaderValue: TryInto, 158 | { 159 | type Output = Result; 160 | 161 | fn apply(mut client: JsonRpcClient, entry: T) -> Self::Output { 162 | let (k, v) = entry.header_pair(); 163 | client.headers.insert(k, v.try_into()?); 164 | Ok(client) 165 | } 166 | } 167 | 168 | impl HeaderEntry for (N, HeaderValue) { 169 | type HeaderName = N; 170 | type HeaderValue = HeaderValue; 171 | 172 | fn header_name(&self) -> &Self::HeaderName { 173 | &self.0 174 | } 175 | 176 | fn header_pair(self) -> (Self::HeaderName, Self::HeaderValue) { 177 | self 178 | } 179 | } 180 | 181 | impl HeaderEntry> for (N, V) 182 | where 183 | N: IntoHeaderName, 184 | V: TryInto, 185 | { 186 | type HeaderName = N; 187 | type HeaderValue = V; 188 | 189 | fn header_name(&self) -> &Self::HeaderName { 190 | &self.0 191 | } 192 | 193 | fn header_pair(self) -> (Self::HeaderName, Self::HeaderValue) { 194 | self 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/auth.rs: -------------------------------------------------------------------------------- 1 | //! Helpers for client authentication. 2 | //! 3 | //! Some RPC nodes will require authentication before requests can be sent to them. 4 | //! 5 | //! This module provides the [`ApiKey`] and [`Authorization`] types that can be used to authenticate 6 | //! requests. 7 | //! 8 | //! ## Example 9 | //! 10 | //! ### API Key (`x-api-key` Header) 11 | //! 12 | //! ``` 13 | //! use near_jsonrpc_client::{JsonRpcClient, auth, methods}; 14 | //! use near_primitives::types::{BlockReference, Finality}; 15 | //! 16 | //! # #[tokio::main] 17 | //! # async fn main() -> Result<(), Box> { 18 | //! let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 19 | //! 20 | //! let client = client.header(auth::ApiKey::new("399ba741-e939-4ffa-8c3c-306ec36fa8de")?); 21 | //! 22 | //! let request = methods::block::RpcBlockRequest { 23 | //! block_reference: BlockReference::Finality(Finality::Final), 24 | //! }; 25 | //! 26 | //! let response = client.call(request).await?; 27 | //! 28 | //! assert!(matches!(response, methods::block::RpcBlockResponse { .. })); 29 | //! # Ok(()) 30 | //! # } 31 | //! ``` 32 | //! 33 | //! ### `Authorization` Header 34 | //! 35 | //! ``` 36 | //! use near_jsonrpc_client::{JsonRpcClient, auth, methods}; 37 | //! use near_primitives::types::{BlockReference, Finality}; 38 | //! 39 | //! # #[tokio::main] 40 | //! # async fn main() -> Result<(), Box> { 41 | //! let client = JsonRpcClient::connect("https://rpc.testnet.near.org") 42 | //! .header(auth::Authorization::bearer( 43 | //! "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", 44 | //! )?); 45 | //! 46 | //! let request = methods::block::RpcBlockRequest { 47 | //! block_reference: BlockReference::Finality(Finality::Final), 48 | //! }; 49 | //! 50 | //! let response = client.call(request).await?; 51 | //! 52 | //! assert!(matches!(response, methods::block::RpcBlockResponse { .. })); 53 | //! # Ok(()) 54 | //! # } 55 | //! ``` 56 | 57 | use std::ops::{Index, RangeFrom}; 58 | use std::str; 59 | 60 | use super::header::{HeaderValue, InvalidHeaderValue, ToStrError}; 61 | 62 | /// NEAR JSON RPC API key. 63 | #[derive(Eq, Hash, Clone, Debug, PartialEq)] 64 | pub struct ApiKey(HeaderValue); 65 | 66 | impl ApiKey { 67 | pub const HEADER_NAME: &'static str = "x-api-key"; 68 | 69 | /// Creates a new API key. 70 | /// 71 | /// See the [`auth`](self) module documentation for more information. 72 | pub fn new>(api_key: K) -> Result { 73 | HeaderValue::from_bytes(api_key.as_ref()).map(|mut api_key| { 74 | ApiKey({ 75 | api_key.set_sensitive(true); 76 | api_key 77 | }) 78 | }) 79 | } 80 | 81 | /// Returns a string slice if the API Key only contains visible ASCII chars. 82 | pub fn to_str(&self) -> Result<&str, ToStrError> { 83 | self.0.to_str() 84 | } 85 | 86 | /// Returns the API key as a byte slice. 87 | pub fn as_bytes(&self) -> &[u8] { 88 | self.0.as_bytes() 89 | } 90 | } 91 | 92 | impl crate::header::HeaderEntry for ApiKey { 93 | type HeaderName = &'static str; 94 | type HeaderValue = HeaderValue; 95 | 96 | fn header_name(&self) -> &Self::HeaderName { 97 | &Self::HEADER_NAME 98 | } 99 | 100 | fn header_pair(self) -> (Self::HeaderName, Self::HeaderValue) { 101 | (Self::HEADER_NAME, self.0) 102 | } 103 | } 104 | 105 | /// HTTP authorization scheme. 106 | #[derive(Eq, Hash, Copy, Clone, Debug, PartialEq)] 107 | #[non_exhaustive] 108 | pub enum AuthorizationScheme { 109 | Bearer, 110 | } 111 | 112 | /// NEAR JSON RPC authorization header. 113 | #[derive(Eq, Hash, Clone, Debug, PartialEq)] 114 | pub struct Authorization(AuthorizationScheme, HeaderValue); 115 | 116 | impl Authorization { 117 | pub const HEADER_NAME: &'static str = "Authorization"; 118 | 119 | /// Creates a new authorization token with the bearer scheme. 120 | /// 121 | /// This does not perform any token-specific validation on the token. 122 | /// 123 | /// See the [`auth`](self) module documentation for more information. 124 | pub fn bearer>(token: T) -> Result { 125 | HeaderValue::from_bytes(&[b"Bearer ", token.as_ref().as_bytes()].concat()).map( 126 | |mut token| { 127 | Authorization(AuthorizationScheme::Bearer, { 128 | token.set_sensitive(true); 129 | token 130 | }) 131 | }, 132 | ) 133 | } 134 | 135 | /// Returns the scheme of the authorization header. 136 | pub fn scheme(&self) -> AuthorizationScheme { 137 | self.0 138 | } 139 | 140 | /// Returns the token as a string slice. 141 | pub fn as_str(&self) -> &str { 142 | unsafe { str::from_utf8_unchecked(self.as_bytes()) } 143 | } 144 | 145 | /// Returns the token as a byte slice. 146 | pub fn as_bytes(&self) -> &[u8] { 147 | self.strip_scheme(self.1.as_bytes()) 148 | } 149 | 150 | fn strip_scheme<'a, T: Index> + ?Sized>(&self, token: &'a T) -> &'a T::Output { 151 | &token[match self.0 { 152 | AuthorizationScheme::Bearer => 7, 153 | }..] 154 | } 155 | } 156 | 157 | impl crate::header::HeaderEntry for Authorization { 158 | type HeaderName = &'static str; 159 | type HeaderValue = HeaderValue; 160 | 161 | fn header_name(&self) -> &Self::HeaderName { 162 | &Self::HEADER_NAME 163 | } 164 | 165 | fn header_pair(self) -> (Self::HeaderName, Self::HeaderValue) { 166 | (Self::HEADER_NAME, self.1) 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | mod tests { 172 | use super::*; 173 | 174 | #[test] 175 | fn sensitive_debug() { 176 | let api_key = ApiKey::new("this is a very secret secret").expect("valid API key"); 177 | 178 | assert_eq!(format!("{:?}", api_key), "ApiKey(Sensitive)"); 179 | 180 | assert_eq!( 181 | api_key.to_str().expect("valid utf8 secret"), 182 | "this is a very secret secret" 183 | ); 184 | 185 | assert_eq!(api_key.as_bytes(), b"this is a very secret secret"); 186 | } 187 | 188 | #[test] 189 | fn bearer_token() { 190 | let token = Authorization::bearer("this is a very secret token").expect("valid token"); 191 | 192 | assert_eq!(format!("{:?}", token), "Authorization(Bearer, Sensitive)"); 193 | 194 | assert_eq!(token.scheme(), AuthorizationScheme::Bearer); 195 | 196 | assert_eq!(token.as_str(), "this is a very secret token"); 197 | 198 | assert_eq!(token.as_bytes(), b"this is a very secret token"); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Error types. 2 | use std::io; 3 | 4 | use thiserror::Error; 5 | 6 | use near_jsonrpc_primitives::errors::{RpcError, RpcErrorKind, RpcRequestValidationErrorKind}; 7 | use near_jsonrpc_primitives::message::{self, Message}; 8 | 9 | /// Potential errors returned while sending a request to the RPC server. 10 | #[derive(Debug, Error)] 11 | pub enum JsonRpcTransportSendError { 12 | /// Client is unable to serialize the request payload before sending it to the server. 13 | #[error("error while serializing payload: [{0}]")] 14 | PayloadSerializeError(io::Error), 15 | /// Client is unable to send the request to the server. 16 | #[error("error while sending payload: [{0}]")] 17 | PayloadSendError(reqwest::Error), 18 | } 19 | 20 | /// Potential errors returned when the client has an issue parsing the response of a method call. 21 | #[derive(Debug, Error)] 22 | pub enum JsonRpcTransportHandlerResponseError { 23 | /// Client fails to deserialize the result of a method call. 24 | #[error("error while parsing method call result: [{0}]")] 25 | ResultParseError(serde_json::Error), 26 | /// Client fails to deserialize the error message returned from a method call. 27 | #[error("error while parsing method call error message: [{0}]")] 28 | ErrorMessageParseError(serde_json::Error), 29 | } 30 | 31 | /// Potential errors returned while receiving responses from an RPC server. 32 | #[derive(Debug, Error)] 33 | pub enum JsonRpcTransportRecvError { 34 | /// Client receives a JSON RPC message body that isn't structured as a response. 35 | #[error("unexpected server response: [{0:?}]")] 36 | UnexpectedServerResponse(Message), 37 | /// Client is unable to read the response from the RPC server. 38 | #[error("error while reading response: [{0}]")] 39 | PayloadRecvError(reqwest::Error), 40 | /// The base response structure is malformed e.g. meta properties like RPC version are missing. 41 | #[error("error while parsing server response: [{0:?}]")] 42 | PayloadParseError(message::Broken), 43 | /// Potential errors returned when the client has an issue parsing the response of a method call. 44 | #[error(transparent)] 45 | ResponseParseError(JsonRpcTransportHandlerResponseError), 46 | } 47 | 48 | /// Potential errors returned while sending requests to or receiving responses from the RPC server. 49 | #[derive(Debug, Error)] 50 | pub enum RpcTransportError { 51 | /// Potential errors returned while sending a request to the RPC server. 52 | #[error(transparent)] 53 | SendError(JsonRpcTransportSendError), 54 | /// Potential errors returned while receiving a response from an RPC server. 55 | #[error(transparent)] 56 | RecvError(JsonRpcTransportRecvError), 57 | } 58 | 59 | /// Unexpected status codes returned by the RPC server. 60 | #[derive(Debug, Error)] 61 | pub enum JsonRpcServerResponseStatusError { 62 | /// The RPC client is unauthorized. 63 | #[error("this client is unauthorized")] 64 | Unauthorized, 65 | /// The RPC client exceeds the rate limit by sending too many requests. 66 | #[error("this client has exceeded the rate limit")] 67 | TooManyRequests, 68 | #[error("the server returned status code 400 - bad request")] 69 | BadRequest, 70 | #[error("the request failed with timeout error")] 71 | TimeoutError, 72 | #[error("the server is unavailable")] 73 | ServiceUnavailable, 74 | /// The RPC server returned a non-200 status code. 75 | #[error("the server returned a non-OK (200) status code: [{status}]")] 76 | Unexpected { status: reqwest::StatusCode }, 77 | } 78 | 79 | /// Potential errors returned by the RPC server. 80 | #[derive(Debug, Error)] 81 | pub enum JsonRpcServerError { 82 | /// An invalid RPC method is called or the RPC methdo is unable to parse the provided arguments. 83 | #[error("request validation error: [{0:?}]")] 84 | RequestValidationError(RpcRequestValidationErrorKind), 85 | /// RPC method call error. 86 | #[error("handler error: [{0}]")] 87 | HandlerError(E), 88 | /// The RPC server returned an internal server error. 89 | #[error("internal error: [{info:?}]")] 90 | InternalError { info: Option }, 91 | /// The RPC server returned a response without context i.e. a response the client doesn't expect. 92 | #[error("error response lacks context: {0}")] 93 | NonContextualError(RpcError), 94 | /// Unexpected status codes returned by the RPC server. 95 | #[error(transparent)] 96 | ResponseStatusError(JsonRpcServerResponseStatusError), 97 | } 98 | 99 | /// Potential errors returned by the RPC client. 100 | #[derive(Debug, Error)] 101 | pub enum JsonRpcError { 102 | /// Potential errors returned while sending requests to or receiving responses from the RPC server. 103 | #[error(transparent)] 104 | TransportError(RpcTransportError), 105 | /// Potential errors returned by the RPC server. 106 | #[error(transparent)] 107 | ServerError(JsonRpcServerError), 108 | } 109 | 110 | impl JsonRpcError { 111 | pub fn handler_error(&self) -> Option<&E> { 112 | if let Self::ServerError(JsonRpcServerError::HandlerError(err)) = self { 113 | return Some(err); 114 | } 115 | None 116 | } 117 | } 118 | 119 | impl From for JsonRpcError { 120 | fn from(err: RpcError) -> Self { 121 | let mut handler_parse_error = None; 122 | match err.error_struct { 123 | Some(RpcErrorKind::HandlerError(ref handler_error)) => { 124 | match E::parse(*handler_error.clone()) { 125 | Ok(handler_error) => { 126 | return JsonRpcError::ServerError(JsonRpcServerError::HandlerError( 127 | handler_error, 128 | )) 129 | } 130 | Err(err) => { 131 | handler_parse_error.replace(err); 132 | } 133 | } 134 | } 135 | Some(RpcErrorKind::RequestValidationError(err)) => { 136 | return JsonRpcError::ServerError(JsonRpcServerError::RequestValidationError(err)); 137 | } 138 | Some(RpcErrorKind::InternalError(err)) => { 139 | return JsonRpcError::ServerError(JsonRpcServerError::InternalError { 140 | info: err["info"]["error_message"] 141 | .as_str() 142 | .map(|info| info.to_string()), 143 | }) 144 | } 145 | None => {} 146 | } 147 | if let Some(ref raw_err_data) = err.data { 148 | match E::parse_legacy_error(*raw_err_data.clone()) { 149 | Some(Ok(handler_error)) => { 150 | return JsonRpcError::ServerError(JsonRpcServerError::HandlerError( 151 | handler_error, 152 | )) 153 | } 154 | Some(Err(err)) => { 155 | handler_parse_error.replace(err); 156 | } 157 | None => {} 158 | } 159 | } 160 | if let Some(err) = handler_parse_error { 161 | return JsonRpcError::TransportError(RpcTransportError::RecvError( 162 | JsonRpcTransportRecvError::ResponseParseError( 163 | JsonRpcTransportHandlerResponseError::ErrorMessageParseError(err), 164 | ), 165 | )); 166 | } 167 | JsonRpcError::ServerError(JsonRpcServerError::NonContextualError(err)) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/methods/experimental/changes.rs: -------------------------------------------------------------------------------- 1 | //! Returns account changes from transactions in a given account. 2 | //! 3 | //! The `RpcStateChangesInBlockByTypeRequest` struct takes in a `BlockReference` and a `StateChangesRequestView`, and returns an `RpcStateChangesInBlockResponse`. 4 | //! 5 | //! ## Examples 6 | //! 7 | //! The `StateChangesRequestView` enum has a couple of variants that can be used to specify what kind of changes to return. 8 | //! 9 | //! - `AccountChanges` 10 | //! 11 | //! ``` 12 | //! # use near_jsonrpc_client::{methods, JsonRpcClient}; 13 | //! use near_primitives::{views::StateChangesRequestView, types::{BlockReference, BlockId}}; 14 | //! 15 | //! # #[tokio::main] 16 | //! # async fn main() -> Result<(), Box> { 17 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 18 | //! 19 | //! let request = methods::EXPERIMENTAL_changes::RpcStateChangesInBlockByTypeRequest { 20 | //! block_reference: BlockReference::BlockId(BlockId::Hash("94yBWhN848vHMnKcw5DxgBQWJW6JHRXnXD6FCLJGjxMU".parse()?)), 21 | //! state_changes_request: StateChangesRequestView::AccountChanges { 22 | //! account_ids: vec!["fido.testnet".parse()?, "rpc_docs.testnet".parse()?], 23 | //! } 24 | //! }; 25 | //! 26 | //! let response = client.call(request).await?; 27 | //! 28 | //! assert!(matches!( 29 | //! response, 30 | //! methods::EXPERIMENTAL_changes::RpcStateChangesInBlockResponse { .. } 31 | //! )); 32 | //! # Ok(()) 33 | //! # } 34 | //! ``` 35 | //! 36 | //! - `SingleAccessKeyChanges` 37 | //! 38 | //! ``` 39 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 40 | //! use near_primitives::{views::StateChangesRequestView, types::{BlockReference, BlockId, AccountWithPublicKey}}; 41 | //! 42 | //! # #[tokio::main] 43 | //! # async fn main() -> Result<(), Box> { 44 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 45 | //! 46 | //! let request = methods::EXPERIMENTAL_changes::RpcStateChangesInBlockByTypeRequest { 47 | //! block_reference: BlockReference::BlockId(BlockId::Hash("94yBWhN848vHMnKcw5DxgBQWJW6JHRXnXD6FCLJGjxMU".parse()?)), 48 | //! state_changes_request: StateChangesRequestView::SingleAccessKeyChanges { 49 | //! keys: vec![ 50 | //! AccountWithPublicKey { 51 | //! account_id: "fido.testnet".parse()?, 52 | //! public_key: "ed25519:GwRkfEckaADh5tVxe3oMfHBJZfHAJ55TRWqJv9hSpR38".parse()?, 53 | //! }, 54 | //! AccountWithPublicKey { 55 | //! account_id: "rpc_docs.testnet".parse()?, 56 | //! public_key: "ed25519:FxGiXr6Dgn92kqBqbQzuoYdKngiizCnywpaN7ALar3Vv".parse()?, 57 | //! } 58 | //! 59 | //! ], 60 | //! } 61 | //! }; 62 | //! 63 | //! let response = client.call(request).await?; 64 | //! 65 | //! assert!(matches!( 66 | //! response, 67 | //! methods::EXPERIMENTAL_changes::RpcStateChangesInBlockResponse { .. } 68 | //! )); 69 | //! # Ok(()) 70 | //! # } 71 | //! ``` 72 | //! 73 | //! - `AllAccessKeyChanges` 74 | //! 75 | //! ``` 76 | //! # use near_jsonrpc_client::{methods, JsonRpcClient}; 77 | //! use near_primitives::{views::StateChangesRequestView, types::{BlockReference, BlockId}}; 78 | //! 79 | //! # #[tokio::main] 80 | //! # async fn main() -> Result<(), Box> { 81 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 82 | //! 83 | //! let request = methods::EXPERIMENTAL_changes::RpcStateChangesInBlockByTypeRequest { 84 | //! block_reference: BlockReference::BlockId(BlockId::Hash("94yBWhN848vHMnKcw5DxgBQWJW6JHRXnXD6FCLJGjxMU".parse()?)), 85 | //! state_changes_request: StateChangesRequestView::AllAccessKeyChanges { 86 | //! account_ids: vec!["fido.testnet".parse()?, "rpc_docs.testnet".parse()?], 87 | //! } 88 | //! }; 89 | //! 90 | //! let response = client.call(request).await?; 91 | //! 92 | //! assert!(matches!( 93 | //! response, 94 | //! methods::EXPERIMENTAL_changes::RpcStateChangesInBlockResponse { .. } 95 | //! )); 96 | //! # Ok(()) 97 | //! # } 98 | //! ``` 99 | //! 100 | //! - `ContractCodeChanges` 101 | //! 102 | //! ``` 103 | //! # use near_jsonrpc_client::{methods, JsonRpcClient}; 104 | //! use near_primitives::{views::StateChangesRequestView, types::{BlockReference, BlockId}}; 105 | //! 106 | //! # #[tokio::main] 107 | //! # async fn main() -> Result<(), Box> { 108 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 109 | //! 110 | //! let request = methods::EXPERIMENTAL_changes::RpcStateChangesInBlockByTypeRequest { 111 | //! block_reference: BlockReference::BlockId(BlockId::Hash("94yBWhN848vHMnKcw5DxgBQWJW6JHRXnXD6FCLJGjxMU".parse()?)), 112 | //! state_changes_request: StateChangesRequestView::ContractCodeChanges { 113 | //! account_ids: vec!["fido.testnet".parse()?, "rpc_docs.testnet".parse()?], 114 | //! } 115 | //! }; 116 | //! 117 | //! let response = client.call(request).await?; 118 | //! 119 | //! assert!(matches!( 120 | //! response, 121 | //! methods::EXPERIMENTAL_changes::RpcStateChangesInBlockResponse { .. } 122 | //! )); 123 | //! # Ok(()) 124 | //! # } 125 | //! ``` 126 | //! 127 | //! - `DataChanges` 128 | //! 129 | //! ``` 130 | //! # use near_jsonrpc_client::{methods, JsonRpcClient}; 131 | //! use near_primitives::{views::StateChangesRequestView, types::{BlockReference, BlockId, StoreKey}, hash::CryptoHash}; 132 | //! 133 | //! # #[tokio::main] 134 | //! # async fn main() -> Result<(), Box> { 135 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 136 | //! 137 | //! let request = methods::EXPERIMENTAL_changes::RpcStateChangesInBlockByTypeRequest { 138 | //! block_reference: BlockReference::BlockId(BlockId::Hash("94yBWhN848vHMnKcw5DxgBQWJW6JHRXnXD6FCLJGjxMU".parse::()?)), 139 | //! state_changes_request: StateChangesRequestView::DataChanges { 140 | //! account_ids: vec!["fido.testnet".parse()?, "rpc_docs.testnet".parse()?], 141 | //! key_prefix: StoreKey::from(vec![]), 142 | //! } 143 | //! }; 144 | //! 145 | //! let response = client.call(request).await?; 146 | //! 147 | //! assert!(matches!( 148 | //! response, 149 | //! methods::EXPERIMENTAL_changes::RpcStateChangesInBlockResponse { .. } 150 | //! )); 151 | //! # Ok(()) 152 | //! # } 153 | //! ``` 154 | use super::*; 155 | 156 | pub use near_jsonrpc_primitives::types::changes::{ 157 | RpcStateChangesError, RpcStateChangesInBlockByTypeRequest, RpcStateChangesInBlockResponse, 158 | }; 159 | 160 | impl RpcHandlerResponse for RpcStateChangesInBlockResponse {} 161 | 162 | impl RpcMethod for RpcStateChangesInBlockByTypeRequest { 163 | type Response = RpcStateChangesInBlockResponse; 164 | type Error = RpcStateChangesError; 165 | 166 | fn method_name(&self) -> &str { 167 | "EXPERIMENTAL_changes" 168 | } 169 | 170 | fn params(&self) -> Result { 171 | Ok(json!(self)) 172 | } 173 | } 174 | 175 | impl private::Sealed for RpcStateChangesInBlockByTypeRequest {} 176 | -------------------------------------------------------------------------------- /src/methods/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains all the RPC methods. 2 | use std::io; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | use serde_json::json; 6 | use thiserror::Error; 7 | 8 | mod private { 9 | pub trait Sealed {} 10 | } 11 | 12 | /// A trait identifying valid NEAR JSON-RPC methods. 13 | pub trait RpcMethod: private::Sealed 14 | where 15 | Self::Response: RpcHandlerResponse, 16 | Self::Error: RpcHandlerError, 17 | { 18 | type Response; 19 | type Error; 20 | 21 | fn method_name(&self) -> &str; 22 | 23 | fn params(&self) -> Result; 24 | 25 | fn parse_handler_response( 26 | response: serde_json::Value, 27 | ) -> Result, serde_json::Error> { 28 | Self::Response::parse(response).map(Ok) 29 | } 30 | } 31 | 32 | impl private::Sealed for &T where T: private::Sealed {} 33 | impl RpcMethod for &T 34 | where 35 | T: RpcMethod, 36 | { 37 | type Response = T::Response; 38 | type Error = T::Error; 39 | 40 | fn method_name(&self) -> &str { 41 | T::method_name(self) 42 | } 43 | 44 | fn params(&self) -> Result { 45 | T::params(self) 46 | } 47 | 48 | fn parse_handler_response( 49 | response: serde_json::Value, 50 | ) -> Result, serde_json::Error> { 51 | T::parse_handler_response(response) 52 | } 53 | } 54 | 55 | /// A trait identifying valid NEAR JSON-RPC method responses. 56 | pub trait RpcHandlerResponse: serde::de::DeserializeOwned { 57 | fn parse(value: serde_json::Value) -> Result { 58 | serde_json::from_value(value) 59 | } 60 | } 61 | 62 | /// A trait identifying valid NEAR JSON-RPC errors. 63 | pub trait RpcHandlerError: serde::de::DeserializeOwned { 64 | /// Parser for the `.error_struct` field in RpcError. 65 | fn parse(handler_error: serde_json::Value) -> Result { 66 | serde_json::from_value(handler_error) 67 | } 68 | 69 | /// Parser for the `.data` field in RpcError, not `.error_struct`. 70 | /// 71 | /// This would only ever be used as a fallback if [`RpcHandlerError::parse`] fails. 72 | /// 73 | /// Defaults to `None` meaning there's no alternative deserialization available. 74 | fn parse_legacy_error(_error: serde_json::Value) -> Option> { 75 | None 76 | } 77 | } 78 | 79 | pub mod block; 80 | pub mod broadcast_tx_async; 81 | pub mod broadcast_tx_commit; 82 | pub mod chunk; 83 | pub mod gas_price; 84 | pub mod health; 85 | pub mod light_client_proof; 86 | pub mod network_info; 87 | pub mod next_light_client_block; 88 | pub mod query; 89 | pub mod send_tx; 90 | pub mod status; 91 | pub mod tx; 92 | pub mod validators; 93 | 94 | // ======== experimental ======== 95 | mod experimental; 96 | pub use experimental::EXPERIMENTAL_changes; 97 | pub use experimental::EXPERIMENTAL_changes_in_block; 98 | pub use experimental::EXPERIMENTAL_genesis_config; 99 | pub use experimental::EXPERIMENTAL_protocol_config; 100 | pub use experimental::EXPERIMENTAL_receipt; 101 | pub use experimental::EXPERIMENTAL_tx_status; 102 | pub use experimental::EXPERIMENTAL_validators_ordered; 103 | // ======== experimental ======== 104 | 105 | // ======== any ======== 106 | #[cfg(feature = "any")] 107 | mod any; 108 | #[cfg(feature = "any")] 109 | pub use any::{request as any, RpcAnyRequest}; 110 | // ======== any ======== 111 | 112 | // ======== sandbox ======== 113 | #[cfg(feature = "sandbox")] 114 | mod sandbox; 115 | 116 | #[cfg(feature = "sandbox")] 117 | pub use sandbox::sandbox_patch_state; 118 | 119 | #[cfg(feature = "sandbox")] 120 | pub use sandbox::sandbox_fast_forward; 121 | // ======== sandbox ======== 122 | 123 | // ======== adversarial ======== 124 | #[cfg(feature = "adversarial")] 125 | mod adversarial; 126 | 127 | #[cfg(feature = "adversarial")] 128 | pub use adversarial::adv_set_weight; 129 | 130 | #[cfg(feature = "adversarial")] 131 | pub use adversarial::adv_disable_header_sync; 132 | 133 | #[cfg(feature = "adversarial")] 134 | pub use adversarial::adv_disable_doomslug; 135 | 136 | #[cfg(feature = "adversarial")] 137 | pub use adversarial::adv_produce_blocks; 138 | 139 | #[cfg(feature = "adversarial")] 140 | pub use adversarial::adv_switch_to_height; 141 | 142 | #[cfg(feature = "adversarial")] 143 | pub use adversarial::adv_get_saved_blocks; 144 | 145 | #[cfg(feature = "adversarial")] 146 | pub use adversarial::adv_check_store; 147 | // ======== adversarial ======== 148 | 149 | /// Converts an RPC Method into JSON. 150 | pub fn to_json(method: &M) -> Result { 151 | let request_payload = near_jsonrpc_primitives::message::Message::request( 152 | method.method_name().to_string(), 153 | method.params()?, 154 | ); 155 | 156 | Ok(json!(request_payload)) 157 | } 158 | 159 | mod common { 160 | use super::*; 161 | 162 | // workaround for deserializing partially serialized 163 | // error types missing the `error_message` field in 164 | // their UnknownBlock variants. 165 | macro_rules! _parse_unknown_block { 166 | ($json:expr => $err_ty:ident) => { 167 | match $json { 168 | err => { 169 | if err["name"] == "UNKNOWN_BLOCK" { 170 | Ok($err_ty::UnknownBlock { 171 | error_message: "".to_string(), 172 | }) 173 | } else { 174 | serde_json::from_value(err) 175 | } 176 | } 177 | } 178 | }; 179 | } 180 | pub(crate) use _parse_unknown_block as parse_unknown_block; 181 | 182 | pub fn serialize_signed_transaction( 183 | tx: &near_primitives::transaction::SignedTransaction, 184 | ) -> Result { 185 | Ok(near_primitives::serialize::to_base64(&borsh::to_vec(&tx)?)) 186 | } 187 | 188 | // adv_* 189 | #[cfg(feature = "adversarial")] 190 | impl RpcHandlerError for () {} 191 | 192 | // adv_* 193 | #[cfg(feature = "adversarial")] 194 | impl RpcHandlerResponse for () { 195 | fn parse(_value: serde_json::Value) -> Result { 196 | Ok(()) 197 | } 198 | } 199 | 200 | #[cfg(feature = "any")] 201 | impl RpcHandlerResponse for serde_json::Value { 202 | fn parse(value: serde_json::Value) -> Result { 203 | Ok(value) 204 | } 205 | } 206 | 207 | #[cfg(feature = "any")] 208 | impl RpcHandlerError for serde_json::Value { 209 | fn parse(handler_error: serde_json::Value) -> Result { 210 | Ok(handler_error) 211 | } 212 | } 213 | 214 | // broadcast_tx_commit, tx 215 | impl RpcHandlerResponse for near_primitives::views::FinalExecutionOutcomeView {} 216 | 217 | // broadcast_tx_commit, tx, EXPERIMENTAL_tx_status 218 | impl RpcHandlerError for near_jsonrpc_primitives::types::transactions::RpcTransactionError { 219 | fn parse_legacy_error(value: serde_json::Value) -> Option> { 220 | match serde_json::from_value::(value) { 221 | Ok(near_jsonrpc_primitives::errors::ServerError::TxExecutionError( 222 | near_primitives::errors::TxExecutionError::InvalidTxError(context), 223 | )) => Some(Ok(Self::InvalidTransaction { context })), 224 | Err(err) => Some(Err(err)), 225 | _ => None, 226 | } 227 | } 228 | } 229 | 230 | // health, status 231 | impl RpcHandlerError for near_jsonrpc_primitives::types::status::RpcStatusError {} 232 | 233 | // EXPERIMENTAL_changes, EXPERIMENTAL_changes_in_block 234 | impl RpcHandlerError for near_jsonrpc_primitives::types::changes::RpcStateChangesError { 235 | fn parse(value: serde_json::Value) -> Result { 236 | parse_unknown_block!(value => Self) 237 | } 238 | } 239 | 240 | // send_tx 241 | impl RpcHandlerResponse for near_jsonrpc_primitives::types::transactions::RpcTransactionResponse {} 242 | 243 | // validators, EXPERIMENTAL_validators_ordered 244 | impl RpcHandlerError for near_jsonrpc_primitives::types::validator::RpcValidatorError {} 245 | } 246 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /examples/create_account.rs: -------------------------------------------------------------------------------- 1 | //! Creates an account on the network. 2 | //! 3 | //! Creates either; 4 | //! - a top-level mainnet / testnet account 5 | //! - or a sub-account for any account on the network. 6 | //! 7 | //! top-level account example: `miraclx.near` creates `foobar.near` 8 | //! sub-account example: `miraclx.near` creates `test.miraclx.near` 9 | //! 10 | //! This script is interactive. 11 | 12 | use near_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError; 13 | use near_jsonrpc_client::{methods, JsonRpcClient}; 14 | use near_jsonrpc_primitives::types::query::QueryResponseKind; 15 | use near_jsonrpc_primitives::types::transactions::TransactionInfo; 16 | use near_primitives::gas::Gas; 17 | use near_primitives::hash::CryptoHash; 18 | use near_primitives::transaction::{ 19 | Action, AddKeyAction, CreateAccountAction, FunctionCallAction, Transaction, TransactionV0, 20 | TransferAction, 21 | }; 22 | use near_primitives::types::{AccountId, Balance, BlockReference}; 23 | use near_primitives::views::{FinalExecutionStatus, TxExecutionStatus}; 24 | 25 | use serde_json::json; 26 | use tokio::time; 27 | 28 | mod utils; 29 | 30 | async fn account_exists( 31 | client: &JsonRpcClient, 32 | account_id: &AccountId, 33 | ) -> Result> { 34 | let access_key_query_response = client 35 | .call(methods::query::RpcQueryRequest { 36 | block_reference: BlockReference::latest(), 37 | request: near_primitives::views::QueryRequest::ViewAccount { 38 | account_id: account_id.clone(), 39 | }, 40 | }) 41 | .await; 42 | 43 | match access_key_query_response { 44 | Ok(_) => Ok(true), 45 | Err(near_jsonrpc_client::errors::JsonRpcError::ServerError( 46 | near_jsonrpc_client::errors::JsonRpcServerError::HandlerError( 47 | near_jsonrpc_primitives::types::query::RpcQueryError::UnknownAccount { .. }, 48 | ), 49 | )) => Ok(false), 50 | Err(res) => Err(res)?, 51 | } 52 | } 53 | 54 | async fn get_current_nonce( 55 | client: &JsonRpcClient, 56 | account_id: &AccountId, 57 | public_key: &near_crypto::PublicKey, 58 | ) -> Result, Box> { 59 | let query_response = client 60 | .call(methods::query::RpcQueryRequest { 61 | block_reference: BlockReference::latest(), 62 | request: near_primitives::views::QueryRequest::ViewAccessKey { 63 | account_id: account_id.clone(), 64 | public_key: public_key.clone(), 65 | }, 66 | }) 67 | .await; 68 | 69 | match query_response { 70 | Ok(access_key_query_response) => match access_key_query_response.kind { 71 | QueryResponseKind::AccessKey(access_key) => Ok(Some(( 72 | access_key_query_response.block_hash, 73 | access_key.nonce, 74 | ))), 75 | _ => Err("failed to extract current nonce")?, 76 | }, 77 | Err(near_jsonrpc_client::errors::JsonRpcError::ServerError( 78 | near_jsonrpc_client::errors::JsonRpcServerError::HandlerError( 79 | near_jsonrpc_primitives::types::query::RpcQueryError::UnknownAccessKey { .. }, 80 | ), 81 | )) => Ok(None), 82 | Err(res) => Err(res)?, 83 | } 84 | } 85 | 86 | #[tokio::main] 87 | async fn main() -> Result<(), Box> { 88 | env_logger::init(); 89 | 90 | let client = utils::select_network()?; 91 | 92 | let signer_account_id = loop { 93 | let signer_account_id = utils::input("Enter the creators Account ID: ")?.parse()?; 94 | if account_exists(&client, &signer_account_id).await? { 95 | break signer_account_id; 96 | } 97 | println!("(i) This account doesn't exist, please reenter!"); 98 | }; 99 | 100 | let (signer, latest_hash, current_nonce) = loop { 101 | let signer_secret_key = utils::input("Enter the creators's private key: ")?.parse()?; 102 | 103 | let signer = near_crypto::InMemorySigner::from_secret_key( 104 | signer_account_id.clone(), 105 | signer_secret_key, 106 | ); 107 | 108 | if let Some((latest_hash, current_nonce)) = 109 | get_current_nonce(&client, &signer.get_account_id(), &signer.public_key()).await? 110 | { 111 | break (signer, latest_hash, current_nonce); 112 | } 113 | println!("(i) Invalid access key, please reenter!"); 114 | }; 115 | 116 | let new_account_id = loop { 117 | let new_account_id = utils::input("What's the new Account ID: ")?.parse()?; 118 | if !account_exists(&client, &new_account_id).await? { 119 | break new_account_id; 120 | } 121 | println!("(i) This account already exists, please reenter!"); 122 | }; 123 | 124 | let initial_deposit = loop { 125 | let deposit: f64 = 126 | utils::input("How much do you want to fund this account with (in Ⓝ units)? ")? 127 | .parse()?; 128 | if deposit >= 0.0 { 129 | break ((deposit * 1_000_000.0) as u128) * 1_000_000_000_000_000_000_u128; 130 | } 131 | println!("(i) Enter a non-zero deposit value!"); 132 | }; 133 | 134 | let is_sub_account = new_account_id.is_sub_account_of(&signer.get_account_id()); 135 | let new_key_pair = near_crypto::SecretKey::from_random(near_crypto::KeyType::ED25519); 136 | 137 | let (transaction, expected_output) = if is_sub_account { 138 | ( 139 | TransactionV0 { 140 | signer_id: signer.get_account_id(), 141 | public_key: signer.public_key().clone(), 142 | nonce: current_nonce + 1, 143 | receiver_id: new_account_id.clone(), 144 | block_hash: latest_hash, 145 | actions: vec![ 146 | Action::CreateAccount(CreateAccountAction {}), 147 | Action::AddKey(Box::new(AddKeyAction { 148 | access_key: near_primitives::account::AccessKey { 149 | nonce: 0, 150 | permission: near_primitives::account::AccessKeyPermission::FullAccess, 151 | }, 152 | public_key: new_key_pair.public_key(), 153 | })), 154 | Action::Transfer(TransferAction { 155 | deposit: Balance::from_yoctonear(initial_deposit), 156 | }), 157 | ], 158 | }, 159 | vec![], 160 | ) 161 | } else { 162 | let contract_id = if client.server_addr().ends_with("testnet.near.org") { 163 | "testnet".parse()? 164 | } else if client.server_addr().ends_with("mainnet.near.org") { 165 | "near".parse()? 166 | } else { 167 | Err("can only create non-sub accounts for mainnet / testnet\nconsider creating a sub-account instead")? 168 | }; 169 | ( 170 | TransactionV0 { 171 | signer_id: signer.get_account_id(), 172 | public_key: signer.public_key().clone(), 173 | nonce: current_nonce + 1, 174 | receiver_id: contract_id, 175 | block_hash: latest_hash, 176 | actions: vec![Action::FunctionCall(Box::new(FunctionCallAction { 177 | method_name: "create_account".to_string(), 178 | args: json!({ 179 | "new_account_id": new_account_id, 180 | "new_public_key": new_key_pair.public_key(), 181 | }) 182 | .to_string() 183 | .into_bytes(), 184 | gas: Gas::from_teragas(300), 185 | deposit: Balance::from_yoctonear(initial_deposit), 186 | }))], 187 | }, 188 | b"true".to_vec(), 189 | ) 190 | }; 191 | 192 | println!("============================================================="); 193 | println!("New Account ID: {}", new_account_id); 194 | println!(" Secret Key: {}", new_key_pair); 195 | println!(" Public Key: {}", new_key_pair.public_key()); 196 | println!(" Deposit: {}", initial_deposit); 197 | println!("-------------------------------------------------------------"); 198 | 199 | let request = methods::broadcast_tx_async::RpcBroadcastTxAsyncRequest { 200 | signed_transaction: Transaction::V0(transaction).sign(&signer), 201 | }; 202 | 203 | let sent_at = time::Instant::now(); 204 | 205 | let tx_hash = client.call(request).await?; 206 | 207 | println!(" Tx Hash: {}", tx_hash); 208 | println!("============================================================="); 209 | 210 | loop { 211 | let response = client 212 | .call(methods::tx::RpcTransactionStatusRequest { 213 | transaction_info: TransactionInfo::TransactionId { 214 | tx_hash, 215 | sender_account_id: signer.get_account_id(), 216 | }, 217 | wait_until: TxExecutionStatus::Final, 218 | }) 219 | .await; 220 | let received_at = time::Instant::now(); 221 | let delta = (received_at - sent_at).as_secs(); 222 | 223 | if delta > 60 { 224 | Err("time limit exceeded for the transaction to be recognized")?; 225 | } 226 | 227 | match response { 228 | Ok(tx) => { 229 | // it's fine to unwrap because we asked for finalized tx 230 | let outcome = tx.final_execution_outcome.unwrap().into_outcome(); 231 | match outcome.status { 232 | FinalExecutionStatus::Failure(err) => { 233 | println!("{:#?}", err); 234 | println!("(!) Creating the account failed, check above for full logs"); 235 | break; 236 | } 237 | FinalExecutionStatus::SuccessValue(ref s) => { 238 | if s == &expected_output { 239 | println!("(i) Account successfully created after {}s", delta); 240 | } else { 241 | println!("{:#?}", outcome); 242 | println!("(!) Creating the account failed, check above for full logs"); 243 | } 244 | break; 245 | } 246 | _ => {} 247 | } 248 | } 249 | Err(err) => match err.handler_error() { 250 | Some( 251 | RpcTransactionError::TimeoutError 252 | | RpcTransactionError::UnknownTransaction { .. }, 253 | ) => { 254 | time::sleep(time::Duration::from_secs(2)).await; 255 | continue; 256 | } 257 | _ => Err(err)?, 258 | }, 259 | } 260 | } 261 | 262 | Ok(()) 263 | } 264 | -------------------------------------------------------------------------------- /src/methods/query.rs: -------------------------------------------------------------------------------- 1 | //! This module allows you to make generic requests to the network. 2 | //! 3 | //! The `RpcQueryRequest` struct takes in a [`BlockReference`](https://docs.rs/near-primitives/0.12.0/near_primitives/types/enum.BlockReference.html) and a [`QueryRequest`](https://docs.rs/near-primitives/0.12.0/near_primitives/views/enum.QueryRequest.html). 4 | //! 5 | //! The `BlockReference` enum allows you to specify a block by `Finality`, `BlockId` or `SyncCheckpoint`. 6 | //! 7 | //! The `QueryRequest` enum provides multiple variaints for performing the following actions: 8 | //! - View an account's details 9 | //! - View a contract's code 10 | //! - View the state of an account 11 | //! - View the `AccessKey` of an account 12 | //! - View the `AccessKeyList` of an account 13 | //! - Call a function in a contract deployed on the network. 14 | //! 15 | //! ## Examples 16 | //! 17 | //! ### Returns basic account information. 18 | //! 19 | //! ``` 20 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 21 | //! use near_primitives::{types::{BlockReference, BlockId}, views::QueryRequest}; 22 | //! 23 | //! # #[tokio::main] 24 | //! # async fn main() -> Result<(), Box> { 25 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 26 | //! 27 | //! let request = methods::query::RpcQueryRequest { 28 | //! block_reference: BlockReference::BlockId(BlockId::Hash("6Qq9hYG7vQhnje4iC1hfbyhh9vNQoNem7j8Dxi7EVSdN".parse()?)), 29 | //! request: QueryRequest::ViewAccount { 30 | //! account_id: "itranscend.near".parse()?, 31 | //! } 32 | //! }; 33 | //! 34 | //! let response = client.call(request).await?; 35 | //! 36 | //! assert!(matches!( 37 | //! response, 38 | //! methods::query::RpcQueryResponse { .. } 39 | //! )); 40 | //! # Ok(()) 41 | //! # } 42 | //! ``` 43 | //! 44 | //! ### Returns the contract code (Wasm binary) deployed to the account. The returned code will be encoded in base64. 45 | //! 46 | //! ``` 47 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 48 | //! use near_primitives::{types::{BlockReference, BlockId}, views::QueryRequest}; 49 | //! 50 | //! # #[tokio::main] 51 | //! # async fn main() -> Result<(), Box> { 52 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 53 | //! 54 | //! let request = methods::query::RpcQueryRequest { 55 | //! block_reference: BlockReference::BlockId(BlockId::Hash("CrYzVUyam5TMJTcJDJMSJ7Fzc79SDTgtK1SfVpEnteZF".parse()?)), 56 | //! request: QueryRequest::ViewCode { 57 | //! account_id: "nosedive.testnet".parse()?, 58 | //! } 59 | //! }; 60 | //! 61 | //! let response = client.call(request).await?; 62 | //! 63 | //! assert!(matches!( 64 | //! response, 65 | //! methods::query::RpcQueryResponse { .. } 66 | //! )); 67 | //! # Ok(()) 68 | //! # } 69 | //! ``` 70 | //! 71 | //! ### Returns the account state 72 | //! 73 | //! ``` 74 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 75 | //! use near_primitives::{types::{BlockReference, BlockId, StoreKey}, views::QueryRequest}; 76 | //! 77 | //! # #[tokio::main] 78 | //! # async fn main() -> Result<(), Box> { 79 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 80 | //! 81 | //! let request = methods::query::RpcQueryRequest { 82 | //! // block_reference: BlockReference::BlockId(BlockId::Hash("AUDcb2iNUbsmCsmYGfGuKzyXKimiNcCZjBKTVsbZGnoH".parse()?)), 83 | //! block_reference: BlockReference::latest(), 84 | //! request: QueryRequest::ViewState { 85 | //! account_id: "nosedive.testnet".parse()?, 86 | //! prefix: StoreKey::from(vec![]), 87 | //! include_proof: false, 88 | //! } 89 | //! }; 90 | //! 91 | //! let response = client.call(request).await?; 92 | //! 93 | //! assert!(matches!( 94 | //! response, 95 | //! methods::query::RpcQueryResponse { .. } 96 | //! )); 97 | //! # Ok(()) 98 | //! # } 99 | //! ``` 100 | //! 101 | //! ### Returns information about a single access key for given account 102 | //! 103 | //! ``` 104 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 105 | //! use near_primitives::{types::{BlockReference, BlockId}, views::QueryRequest}; 106 | //! 107 | //! # #[tokio::main] 108 | //! # async fn main() -> Result<(), Box> { 109 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 110 | //! 111 | //! let request = methods::query::RpcQueryRequest { 112 | //! // block_reference: BlockReference::BlockId(BlockId::Hash("CA9bigchLBUYKaHKz3vQxK3Z7Fae2gnVabGrrLJrQEzp".parse()?)), 113 | //! block_reference: BlockReference::latest(), 114 | //! request: QueryRequest::ViewAccessKey { 115 | //! account_id: "fido.testnet".parse()?, 116 | //! public_key: "ed25519:GwRkfEckaADh5tVxe3oMfHBJZfHAJ55TRWqJv9hSpR38".parse()? 117 | //! } 118 | //! }; 119 | //! 120 | //! let response = client.call(request).await?; 121 | //! 122 | //! assert!(matches!( 123 | //! response, 124 | //! methods::query::RpcQueryResponse { .. } 125 | //! )); 126 | //! # Ok(()) 127 | //! # } 128 | //! ``` 129 | //! 130 | //! ### Returns all access keys for a given account. 131 | //! 132 | //! ``` 133 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 134 | //! use near_primitives::{types::{BlockReference, BlockId}, views::QueryRequest}; 135 | //! 136 | //! # #[tokio::main] 137 | //! # async fn main() -> Result<(), Box> { 138 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 139 | //! 140 | //! let request = methods::query::RpcQueryRequest { 141 | //! block_reference: BlockReference::BlockId(BlockId::Hash("AUDcb2iNUbsmCsmYGfGuKzyXKimiNcCZjBKTVsbZGnoH".parse()?)), 142 | //! request: QueryRequest::ViewAccessKeyList { 143 | //! account_id: "nosedive.testnet".parse()?, 144 | //! } 145 | //! }; 146 | //! 147 | //! let response = client.call(request).await?; 148 | //! 149 | //! assert!(matches!( 150 | //! response, 151 | //! methods::query::RpcQueryResponse { .. } 152 | //! )); 153 | //! # Ok(()) 154 | //! # } 155 | //! ``` 156 | //! 157 | //! ### Call a function in a contract deployed on the network 158 | //! 159 | //! ``` 160 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 161 | //! use near_primitives::{types::{BlockReference, BlockId, FunctionArgs}, views::QueryRequest}; 162 | //! use serde_json::json; 163 | //! 164 | //! # #[tokio::main] 165 | //! # async fn main() -> Result<(), Box> { 166 | //! let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 167 | //! 168 | //! let request = methods::query::RpcQueryRequest { 169 | //! // block_reference: BlockReference::BlockId(BlockId::Hash("CA9bigchLBUYKaHKz3vQxK3Z7Fae2gnVabGrrLJrQEzp".parse()?)), 170 | //! block_reference: BlockReference::latest(), 171 | //! request: QueryRequest::CallFunction { 172 | //! account_id: "nosedive.testnet".parse()?, 173 | //! method_name: "status".parse()?, 174 | //! args: FunctionArgs::from( 175 | //! json!({ 176 | //! "account_id": "miraclx.testnet", 177 | //! }) 178 | //! .to_string() 179 | //! .into_bytes(), 180 | //! ) 181 | //! } 182 | //! }; 183 | //! 184 | //! let response = client.call(request).await?; 185 | //! 186 | //! assert!(matches!( 187 | //! response, 188 | //! methods::query::RpcQueryResponse { .. } 189 | //! )); 190 | //! # Ok(()) 191 | //! # } 192 | //! ``` 193 | use super::*; 194 | 195 | pub use near_jsonrpc_primitives::types::query::{RpcQueryError, RpcQueryRequest, RpcQueryResponse}; 196 | 197 | impl RpcHandlerResponse for RpcQueryResponse {} 198 | 199 | impl RpcHandlerError for RpcQueryError {} 200 | 201 | impl private::Sealed for RpcQueryRequest {} 202 | 203 | impl RpcMethod for RpcQueryRequest { 204 | type Response = RpcQueryResponse; 205 | type Error = RpcQueryError; 206 | 207 | fn method_name(&self) -> &str { 208 | "query" 209 | } 210 | 211 | fn params(&self) -> Result { 212 | Ok(json!(self)) 213 | } 214 | 215 | fn parse_handler_response( 216 | response: serde_json::Value, 217 | ) -> Result, serde_json::Error> { 218 | match serde_json::from_value::(response)? { 219 | QueryResponse::HandlerResponse(r) => Ok(Ok(r)), 220 | QueryResponse::HandlerError(LegacyQueryError { 221 | error, 222 | block_height, 223 | block_hash, 224 | }) => { 225 | let mut err_parts = error.split(' '); 226 | let query_error = if let ( 227 | Some("access"), 228 | Some("key"), 229 | Some(pk), 230 | Some("does"), 231 | Some("not"), 232 | Some("exist"), 233 | Some("while"), 234 | Some("viewing"), 235 | None, 236 | ) = ( 237 | err_parts.next(), 238 | err_parts.next(), 239 | err_parts.next(), 240 | err_parts.next(), 241 | err_parts.next(), 242 | err_parts.next(), 243 | err_parts.next(), 244 | err_parts.next(), 245 | err_parts.next(), 246 | ) { 247 | let public_key = pk 248 | .parse::() 249 | .map_err(serde::de::Error::custom)?; 250 | RpcQueryError::UnknownAccessKey { 251 | public_key, 252 | block_height, 253 | block_hash, 254 | } 255 | } else { 256 | RpcQueryError::ContractExecutionError { 257 | vm_error: error, 258 | block_height, 259 | block_hash, 260 | } 261 | }; 262 | 263 | Ok(Err(query_error)) 264 | } 265 | } 266 | } 267 | } 268 | 269 | #[derive(serde::Deserialize)] 270 | #[serde(untagged)] 271 | enum QueryResponse { 272 | HandlerResponse(RpcQueryResponse), 273 | HandlerError(LegacyQueryError), 274 | } 275 | 276 | #[derive(serde::Deserialize)] 277 | struct LegacyQueryError { 278 | error: String, 279 | block_height: near_primitives::types::BlockHeight, 280 | block_hash: near_primitives::hash::CryptoHash, 281 | } 282 | 283 | #[cfg(test)] 284 | mod tests { 285 | use {super::*, crate::*}; 286 | 287 | /// This test is to make sure the method executor treats `&RpcMethod`s the same as `RpcMethod`s. 288 | #[tokio::test] 289 | async fn test_unknown_method() -> Result<(), Box> { 290 | let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 291 | 292 | let request = RpcQueryRequest { 293 | block_reference: near_primitives::types::BlockReference::latest(), 294 | request: near_primitives::views::QueryRequest::CallFunction { 295 | account_id: "testnet".parse()?, 296 | method_name: "some_unavailable_method".to_string(), 297 | args: vec![].into(), 298 | }, 299 | }; 300 | 301 | let response_err = client.call(&request).await.unwrap_err(); 302 | 303 | assert!( 304 | matches!( 305 | response_err.handler_error(), 306 | Some(RpcQueryError::ContractExecutionError { 307 | ref vm_error, 308 | .. 309 | }) if vm_error.contains("MethodResolveError(MethodNotFound)") 310 | ), 311 | "this is unexpected: {:#?}", 312 | response_err 313 | ); 314 | 315 | Ok(()) 316 | } 317 | 318 | #[tokio::test] 319 | async fn test_unknown_access_key() -> Result<(), Box> { 320 | let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 321 | 322 | let request = RpcQueryRequest { 323 | block_reference: near_primitives::types::BlockReference::BlockId( 324 | near_primitives::types::BlockId::Height(63503911), 325 | ), 326 | request: near_primitives::views::QueryRequest::ViewAccessKey { 327 | account_id: "miraclx.testnet".parse()?, 328 | public_key: "ed25519:9KnjTjL6vVoM8heHvCcTgLZ67FwFkiLsNtknFAVsVvYY".parse()?, 329 | }, 330 | }; 331 | 332 | let response_err = client.call(request).await.unwrap_err(); 333 | 334 | assert!( 335 | matches!( 336 | response_err.handler_error(), 337 | Some(RpcQueryError::UnknownAccessKey { 338 | ref public_key, 339 | block_height: 63503911, 340 | .. 341 | }) if public_key.to_string() == "ed25519:9KnjTjL6vVoM8heHvCcTgLZ67FwFkiLsNtknFAVsVvYY" 342 | ), 343 | "this is unexpected: {:#?}", 344 | response_err 345 | ); 346 | 347 | Ok(()) 348 | } 349 | 350 | #[tokio::test] 351 | async fn test_contract_execution_error() -> Result<(), Box> { 352 | let client = JsonRpcClient::connect("https://archival-rpc.testnet.fastnear.com"); 353 | 354 | let request = RpcQueryRequest { 355 | block_reference: near_primitives::types::BlockReference::BlockId( 356 | near_primitives::types::BlockId::Height(63503911), 357 | ), 358 | request: near_primitives::views::QueryRequest::CallFunction { 359 | account_id: "miraclx.testnet".parse()?, 360 | method_name: "".to_string(), 361 | args: vec![].into(), 362 | }, 363 | }; 364 | 365 | let response_err = client.call(request).await.unwrap_err(); 366 | 367 | assert!( 368 | matches!( 369 | response_err.handler_error(), 370 | Some(RpcQueryError::ContractExecutionError { 371 | ref vm_error, 372 | block_height: 63503911, 373 | .. 374 | }) if vm_error.contains("MethodResolveError(MethodEmptyName)") 375 | ), 376 | "this is unexpected: {:#?}", 377 | response_err 378 | ); 379 | 380 | Ok(()) 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.20.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.19.0...v0.20.0) - 2025-12-01 11 | 12 | ### Other 13 | 14 | - [**breaking**] upgraded crates to nearcore 2.10 release ([#180](https://github.com/near/near-jsonrpc-client-rs/pull/180)) 15 | 16 | ## [0.18.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.17.0...v0.18.0) - 2025-08-15 17 | 18 | ### Other 19 | 20 | - [**breaking**] update near-* dependencies to 0.31 release ([#177](https://github.com/near/near-jsonrpc-client-rs/pull/177)) 21 | 22 | ## [0.17.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.16.1...v0.17.0) - 2025-05-09 23 | 24 | ### Other 25 | 26 | - [**breaking**] updates near-* dependencies to 0.30 release ([#174](https://github.com/near/near-jsonrpc-client-rs/pull/174)) 27 | 28 | ## [0.16.1](https://github.com/near/near-jsonrpc-client-rs/compare/v0.16.0...v0.16.1) - 2025-03-27 29 | 30 | ### Fixed 31 | 32 | - Fixed missing serde::Serialize on BroadcastTxAsync and GenesisConfig errors ([#171](https://github.com/near/near-jsonrpc-client-rs/pull/171)) 33 | 34 | ## [0.16.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.15.1...v0.16.0) - 2025-03-06 35 | 36 | ### Other 37 | 38 | - [**breaking**] updates near-* dependencies to 0.29 release ([#169](https://github.com/near/near-jsonrpc-client-rs/pull/169)) 39 | - added CODEOWNERS ([#167](https://github.com/near/near-jsonrpc-client-rs/pull/167)) 40 | 41 | ## [0.15.1](https://github.com/near/near-jsonrpc-client-rs/compare/v0.15.0...v0.15.1) - 2024-12-13 42 | 43 | ### Other 44 | 45 | - fixed test compilation (#165) 46 | 47 | ## [0.15.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.14.0...v0.15.0) - 2024-12-10 48 | 49 | ### Other 50 | 51 | - [**breaking**] updates near-* dependencies to 0.28 release (#163) 52 | 53 | ## [0.14.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.13.0...v0.14.0) - 2024-11-12 54 | 55 | ### Added 56 | 57 | - added http errors 400, 408, 503, 500 ([#160](https://github.com/near/near-jsonrpc-client-rs/pull/160)) 58 | 59 | ### Other 60 | 61 | - [**breaking**] updates near-* dependencies to 0.27 release ([#161](https://github.com/near/near-jsonrpc-client-rs/pull/161)) 62 | 63 | ## [0.13.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.12.0...v0.13.0) - 2024-09-11 64 | 65 | ### Other 66 | 67 | - [**breaking**] updates near-* dependencies to 0.26 release ([#157](https://github.com/near/near-jsonrpc-client-rs/pull/157)) 68 | 69 | ## [0.12.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.11.0...v0.12.0) - 2024-08-21 70 | 71 | ### Other 72 | - updated near-* to 0.25.0 ([#154](https://github.com/near/near-jsonrpc-client-rs/pull/154)) 73 | 74 | ## [0.11.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.10.1...v0.11.0) - 2024-08-09 75 | 76 | ### Other 77 | - updated near-* crates to allow 0.24.0 in addition to all the previously supported versions ([#151](https://github.com/near/near-jsonrpc-client-rs/pull/151)) 78 | 79 | ## [0.10.1](https://github.com/near/near-jsonrpc-client-rs/compare/v0.10.0...v0.10.1) - 2024-06-18 80 | 81 | ### Other 82 | - Updated near-deps to 0.23.0 ([#148](https://github.com/near/near-jsonrpc-client-rs/pull/148)) 83 | 84 | ## [0.10.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.9.0...v0.10.0) - 2024-06-07 85 | 86 | ### Other 87 | - expose `ChunkReference` type used in chunk call ([#144](https://github.com/near/near-jsonrpc-client-rs/pull/144)) 88 | - [**breaking**] Upgraded libraries to the latest versions: near-* 0.22 and reqwest 0.12 ([#145](https://github.com/near/near-jsonrpc-client-rs/pull/145)) 89 | 90 | ## [0.9.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.8.0...v0.9.0) - 2024-04-22 91 | 92 | ### Added 93 | - Upgrade near-primitives to 0.21.x and refactor the API adding `wait_until` flag, drop `check_tx` method ([#136](https://github.com/near/near-jsonrpc-client-rs/pull/136)) 94 | 95 | ### Other 96 | - removed array creation for parameters in fn params() implementation for send_tx method ([#142](https://github.com/near/near-jsonrpc-client-rs/pull/142)) 97 | 98 | ## [0.8.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.7.0...v0.8.0) - 2024-01-21 99 | 100 | ### Other 101 | - [**breaking**] Upgraded NEAR crates to 0.20.0 release ([#137](https://github.com/near/near-jsonrpc-client-rs/pull/137)) 102 | 103 | ## [0.7.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.6.0...v0.7.0) - 2024-01-07 104 | 105 | ### Added 106 | - Added a new example to view contract state ([#129](https://github.com/near/near-jsonrpc-client-rs/pull/129)) 107 | 108 | ### Fixed 109 | - Fixed doc tests after the recent crate updates ([#135](https://github.com/near/near-jsonrpc-client-rs/pull/135)) 110 | 111 | ### Other 112 | - *(docs)* revise crate-level docs ([#88](https://github.com/near/near-jsonrpc-client-rs/pull/88)) 113 | - *(docs)* revise README docs ([#91](https://github.com/near/near-jsonrpc-client-rs/pull/91)) 114 | - *(docs)* revise module-level docs for RPC methods ([#87](https://github.com/near/near-jsonrpc-client-rs/pull/87)) 115 | - *(docs)* document the experimental `tx_status` RPC method ([#63](https://github.com/near/near-jsonrpc-client-rs/pull/63)) 116 | - *(docs)* document the experimental `genesis_config` RPC method ([#64](https://github.com/near/near-jsonrpc-client-rs/pull/64)) 117 | - *(docs)* document the `EXPERIMENTAL_protocol_config` RPC method ([#65](https://github.com/near/near-jsonrpc-client-rs/pull/65)) 118 | - *(docs)* document the `EXPERIMENTAL_changes_in_block` RPC method ([#66](https://github.com/near/near-jsonrpc-client-rs/pull/66)) 119 | - *(docs)* document the `EXPERIMENTAL_receipt` RPC method ([#67](https://github.com/near/near-jsonrpc-client-rs/pull/67)) 120 | - *(docs)* document the `query` RPC method ([#68](https://github.com/near/near-jsonrpc-client-rs/pull/68)) 121 | - *(docs)* document the experimental `validators_ordered` RPC method ([#69](https://github.com/near/near-jsonrpc-client-rs/pull/69)) 122 | - *(docs)* document the `gas_price` RPC method ([#70](https://github.com/near/near-jsonrpc-client-rs/pull/70)) 123 | - *(docs)* document the `EXPERIMENTAL_changes` RPC method ([#71](https://github.com/near/near-jsonrpc-client-rs/pull/71)) 124 | - *(docs)* document the `EXPERIMENTAL_check_tx` RPC method ([#72](https://github.com/near/near-jsonrpc-client-rs/pull/72)) 125 | - *(docs)* document the `sandbox_fast_forward` RPC method ([#75](https://github.com/near/near-jsonrpc-client-rs/pull/75)) 126 | - *(docs)* document the `validators` RPC method ([#76](https://github.com/near/near-jsonrpc-client-rs/pull/76)) 127 | - *(docs)* document the `next_light_client_block` RPC method ([#78](https://github.com/near/near-jsonrpc-client-rs/pull/78)) 128 | - *(docs)* document the `broadcast_tx_async` RPC method ([#79](https://github.com/near/near-jsonrpc-client-rs/pull/79)) 129 | - *(docs)* document the `broadcast_tx_commit` RPC method ([#80](https://github.com/near/near-jsonrpc-client-rs/pull/80)) 130 | - *(docs)* document the `sandbox_patch_state` RPC method ([#81](https://github.com/near/near-jsonrpc-client-rs/pull/81)) 131 | - *(docs)* document the error types and variants in the `errors` module ([#84](https://github.com/near/near-jsonrpc-client-rs/pull/84)) 132 | - *(docs)* document the generic `methods::any()` constructor ([#89](https://github.com/near/near-jsonrpc-client-rs/pull/89)) 133 | - [**breaking**] Bump dependencies ([#134](https://github.com/near/near-jsonrpc-client-rs/pull/134)) 134 | 135 | ## [0.6.0](https://github.com/near/near-jsonrpc-client-rs/compare/v0.5.1...v0.6.0) - 2023-06-02 136 | 137 | ### Other 138 | - [**breaking**] Upgrade near primitive crates version to 0.17.0 ([#126](https://github.com/near/near-jsonrpc-client-rs/pull/126)) 139 | 140 | ## [0.5.1] - 2023-03-22 141 | 142 | - Updated `borsh` to `0.10.2`. 143 | 144 | ## [0.5.0] - 2023-02-24 145 | 146 | ### Added 147 | 148 | - `ApiKey::new` now accepts byte arrays and byte slices. 149 | - `Authorization::bearer` method for token-authenticated requests. 150 | - `ApiKey::as_bytes` returns a byte slice of the key without utf-8 validation. 151 | 152 | ### Changed 153 | 154 | - Updated nearcore dependencies to `0.16.0`, which now requires a MSRV of `1.67.1`. 155 | - `ApiKey::new` no longer requres the input of a valid UUID. 156 | - `Debug` on `ApiKey` doesn't reveal the key anymore. 157 | - The `auth` module is no longer feature gated. 158 | 159 | ### Breaking 160 | 161 | - Removed the `auth::IntoApiKey` trait, any thing you can get a byte slice from is now a valid `ApiKey` input. 162 | - Replaced the `ApiKey::as_str` method with `ApiKey::to_str`, now returning a `Result`. 163 | - Replaced the `InvalidApiKey` error with `InvalidHeaderValue` re-exported from `http`. 164 | - Removed `Display` on `ApiKey`. 165 | 166 | ## [0.4.1] - 2022-11-11 167 | 168 | - Fixed an issue where an `&RpcMethod`'s response was being parsed differently from an `RpcMethod`. 169 | 170 | ## [0.4.0] - 2022-10-04 171 | 172 | - Updated nearcore dependencies, which now requires a MSRV of `1.64.0`. , 173 | - Updated other dependencies, with some general improvements. 174 | - Added `rustls-tls` feature flag to enable `rustls` as an alternative to `native-tls`. 175 | - Switched to using `log::debug!` instead of `log::info!` for debug logging. 176 | - Fixed `gas_price` RPC method serialization. 177 | - Fixed `query` method error deserialization. 178 | - Reworked the `JsonRpcError`::`handler_error` method. 179 | - Moved auth specific logic behind a feature flag. 180 | - Added the `methods::to_json()` helper method for visualizing the serialization of the RPC methods. 181 | 182 | ## [0.4.0-beta.0] - 2022-05-31 183 | 184 |
185 | 186 | 187 | Superseded by 188 | 0.4.0 189 | 190 | 191 | 192 | 193 | > - Updated nearcore dependencies, fixing a previous breaking change. 194 | > - Fixed `gas_price` RPC method serialization. 195 | > - Fixed `query` method error deserialization. 196 | > - Reworked the `JsonRpcError`::`handler_error` method. 197 | > - Moved auth specific logic behind a feature flag. 198 | > - Added the `methods::to_json()` helper method for visualizing the serialization of the RPC methods. 199 | 200 |
201 | 202 | ## [0.3.0] - 2022-02-09 203 | 204 | - Dropped generic authentication and added support for custom headers. 205 | - Added the `sandbox_fast_forward` RPC Method. 206 | - Upgraded `nearcore` crates to `v0.12.0` 207 | - Executing the [examples](https://github.com/near/near-jsonrpc-client-rs/tree/master/examples) now allows custom RPC addr specification with interactive server selection. 208 | - `JsonRpcClient::connect` is now generic over any url-like type. [`Url`](https://docs.rs/url/*/url/struct.Url.html), `&str`, `String` and `&String` are all supported. 209 | - `JsonRpcClient` now defaults to the `Unauthenticated` state, easing a type specification pain point. 210 | 211 | ## [0.2.0] - 2021-12-22 212 | 213 | - Updated nearcore version to `0.11.0` (). 214 | - Fixed `chunk` method serialization. 215 | - `Client::call` no longer consumes the client. 216 | - Implemented workaround for partially serialized server responses. 217 | - Dropped base64 API token support in favor of a generic key-value approach. 218 | - Added examples to repo. 219 | - Ensured `None`-typed wrapped errors are actual errors (i.e. have all traits attributed with errors, especially `fmt::Display`). 220 | 221 | ## [0.1.0] - 2021-11-11 222 | 223 | > Release Page: 224 | 225 | [unreleased]: https://github.com/near/near-jsonrpc-client-rs/compare/v0.4.1...HEAD 226 | [0.4.1]: https://github.com/near/near-jsonrpc-client-rs/compare/v0.4.0...v0.4.1 227 | [0.4.0]: https://github.com/near/near-jsonrpc-client-rs/compare/v0.3.0...v0.4.0 228 | [0.4.0-beta.0]: https://github.com/near/near-jsonrpc-client-rs/compare/v0.3.0...v0.4.0-beta.0 229 | [0.3.0]: https://github.com/near/near-jsonrpc-client-rs/compare/v0.2.0...v0.3.0 230 | [0.2.0]: https://github.com/near/near-jsonrpc-client-rs/compare/v0.1.0...v0.2.0 231 | [0.1.0]: https://github.com/near/near-jsonrpc-client-rs/releases/tag/v0.1.0 232 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Lower-level API for interfacing with the NEAR Protocol via JSONRPC. 2 | //! 3 | //! ## Layout 4 | //! 5 | //! Each one the valid *public* JSON RPC methods are pre-defined in specialized modules within the `methods` module. 6 | //! 7 | //! Inside every method module (e.g [`methods::query`]) there's; 8 | //! - a `Request` type (e.g [`methods::query::RpcQueryRequest`]) 9 | //! - a `Response` type (e.g [`methods::query::RpcQueryResponse`]) 10 | //! - and an `Error` type (e.g [`methods::query::RpcQueryError`]) 11 | //! 12 | //! Calling a constructed request on a client returns with the response and error types for that method. 13 | //! 14 | //! ## Examples 15 | //! 16 | //! 1. Request server status from testnet RPC 17 | //! 18 | //! ``` 19 | //! # #![allow(deprecated)] 20 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 21 | //! 22 | //! # #[tokio::main] 23 | //! # async fn main() -> Result<(), Box> { 24 | //! let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 25 | //! 26 | //! let request = methods::status::RpcStatusRequest; // no params 27 | //! 28 | //! // call a method on the server via the connected client 29 | //! let server_status = client.call(request).await?; 30 | //! 31 | //! println!("{:?}", server_status); 32 | //! # Ok(()) 33 | //! # } 34 | //! ``` 35 | //! 36 | //! 2. Query transaction status from mainnet RPC 37 | //! 38 | //! ```no_run 39 | //! use near_jsonrpc_client::{methods, JsonRpcClient}; 40 | //! use near_jsonrpc_primitives::types::transactions::TransactionInfo; 41 | //! use near_primitives::views::TxExecutionStatus; 42 | //! 43 | //! # #[tokio::main] 44 | //! # async fn main() -> Result<(), Box> { 45 | //! let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 46 | //! 47 | //! let tx_status_request = methods::tx::RpcTransactionStatusRequest { 48 | //! transaction_info: TransactionInfo::TransactionId { 49 | //! tx_hash: "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U".parse()?, 50 | //! sender_account_id: "miraclx.near".parse()?, 51 | //! }, 52 | //! wait_until: TxExecutionStatus::Executed, 53 | //! }; 54 | //! 55 | //! let tx_status = client.call(tx_status_request).await?; 56 | //! 57 | //! println!("{:?}", tx_status); 58 | //! # Ok(()) 59 | //! # } 60 | //! ``` 61 | use std::{fmt, sync::Arc}; 62 | 63 | use lazy_static::lazy_static; 64 | 65 | pub mod auth; 66 | pub mod errors; 67 | pub mod header; 68 | pub mod methods; 69 | 70 | use errors::*; 71 | 72 | pub const NEAR_MAINNET_RPC_URL: &str = "https://rpc.mainnet.near.org"; 73 | pub const NEAR_TESTNET_RPC_URL: &str = "https://rpc.testnet.near.org"; 74 | pub const NEAR_MAINNET_ARCHIVAL_RPC_URL: &str = "https://archival-rpc.mainnet.near.org"; 75 | pub const NEAR_TESTNET_ARCHIVAL_RPC_URL: &str = "https://archival-rpc.testnet.near.org"; 76 | 77 | lazy_static! { 78 | static ref DEFAULT_CONNECTOR: JsonRpcClientConnector = JsonRpcClient::new_client(); 79 | } 80 | 81 | /// NEAR JSON RPC client connector. 82 | #[derive(Clone)] 83 | pub struct JsonRpcClientConnector { 84 | client: reqwest::Client, 85 | } 86 | 87 | impl JsonRpcClientConnector { 88 | /// Return a JsonRpcClient that connects to the specified server. 89 | pub fn connect(&self, server_addr: U) -> JsonRpcClient { 90 | log::debug!("returned a new JSONRPC client handle"); 91 | 92 | JsonRpcClient { 93 | inner: Arc::new(JsonRpcInnerClient { 94 | server_addr: server_addr.to_string(), 95 | client: self.client.clone(), 96 | }), 97 | headers: reqwest::header::HeaderMap::new(), 98 | } 99 | } 100 | } 101 | 102 | struct JsonRpcInnerClient { 103 | server_addr: String, 104 | client: reqwest::Client, 105 | } 106 | 107 | #[derive(Clone)] 108 | /// A NEAR JSON RPC Client. 109 | /// 110 | /// This is the main struct that you will use to interact with the NEAR JSON RPC API. 111 | /// 112 | /// ## Example 113 | /// 114 | ///``` 115 | /// use near_jsonrpc_client::{methods, JsonRpcClient}; 116 | /// use near_primitives::types::{BlockReference, Finality}; 117 | /// 118 | /// # #[tokio::main] 119 | /// # async fn main() -> Result<(), Box> { 120 | /// let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 121 | /// 122 | /// let request = methods::block::RpcBlockRequest { 123 | /// block_reference: BlockReference::Finality(Finality::Final), 124 | /// }; 125 | /// 126 | /// let server_status = client.call(request).await?; 127 | /// 128 | /// println!("{:?}", server_status); 129 | /// # Ok(()) 130 | /// # } 131 | /// ``` 132 | pub struct JsonRpcClient { 133 | inner: Arc, 134 | headers: reqwest::header::HeaderMap, 135 | } 136 | 137 | pub type MethodCallResult = Result>; 138 | 139 | impl JsonRpcClient { 140 | /// Connect to a JSON RPC server using the default connector. 141 | /// 142 | /// It's virtually the same as calling `new_client().connect(server_addr)`. 143 | /// Only, this method optimally reuses the same connector across invocations. 144 | /// 145 | /// ## Example 146 | /// 147 | /// ``` 148 | /// use near_jsonrpc_client::JsonRpcClient; 149 | /// 150 | /// let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 151 | /// ``` 152 | pub fn connect(server_addr: U) -> JsonRpcClient { 153 | DEFAULT_CONNECTOR.connect(server_addr) 154 | } 155 | 156 | /// Get the server address the client connects to. 157 | /// 158 | /// It basically returns the server address passed to `connect()`. 159 | /// 160 | /// ## Example 161 | /// 162 | /// ``` 163 | /// # use near_jsonrpc_client::JsonRpcClient; 164 | /// let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 165 | /// 166 | /// assert_eq!(client.server_addr(), "https://rpc.testnet.near.org"); 167 | /// ``` 168 | pub fn server_addr(&self) -> &str { 169 | &self.inner.server_addr 170 | } 171 | 172 | /// RPC method executor for the client. 173 | /// 174 | /// ## Example 175 | /// 176 | /// ``` 177 | /// use near_jsonrpc_client::{methods, JsonRpcClient}; 178 | /// 179 | /// # #[tokio::main] 180 | /// # async fn main() -> Result<(), Box> { 181 | /// let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 182 | /// 183 | /// let request = methods::status::RpcStatusRequest; 184 | /// let response = client.call(request).await?; 185 | /// 186 | /// assert!(matches!( 187 | /// response, 188 | /// methods::status::RpcStatusResponse { .. } 189 | /// )); 190 | /// # Ok(()) 191 | /// # } 192 | /// ``` 193 | pub async fn call(&self, method: M) -> MethodCallResult 194 | where 195 | M: methods::RpcMethod, 196 | { 197 | let request_payload = methods::to_json(&method).map_err(|err| { 198 | JsonRpcError::TransportError(RpcTransportError::SendError( 199 | JsonRpcTransportSendError::PayloadSerializeError(err), 200 | )) 201 | })?; 202 | 203 | log::debug!("request payload: {:#}", request_payload); 204 | log::debug!("request headers: {:#?}", self.headers()); 205 | 206 | let request_payload = serde_json::to_vec(&request_payload).map_err(|err| { 207 | JsonRpcError::TransportError(RpcTransportError::SendError( 208 | JsonRpcTransportSendError::PayloadSerializeError(err.into()), 209 | )) 210 | })?; 211 | 212 | let request = self 213 | .inner 214 | .client 215 | .post(&self.inner.server_addr) 216 | .headers(self.headers.clone()) 217 | .body(request_payload); 218 | 219 | let response = request.send().await.map_err(|err| { 220 | JsonRpcError::TransportError(RpcTransportError::SendError( 221 | JsonRpcTransportSendError::PayloadSendError(err), 222 | )) 223 | })?; 224 | log::debug!("response headers: {:#?}", response.headers()); 225 | match response.status() { 226 | reqwest::StatusCode::OK => {} 227 | non_ok_status => { 228 | return Err(JsonRpcError::ServerError(match non_ok_status { 229 | reqwest::StatusCode::UNAUTHORIZED => JsonRpcServerError::ResponseStatusError( 230 | JsonRpcServerResponseStatusError::Unauthorized, 231 | ), 232 | reqwest::StatusCode::TOO_MANY_REQUESTS => { 233 | JsonRpcServerError::ResponseStatusError( 234 | JsonRpcServerResponseStatusError::TooManyRequests, 235 | ) 236 | } 237 | reqwest::StatusCode::BAD_REQUEST => JsonRpcServerError::ResponseStatusError( 238 | JsonRpcServerResponseStatusError::BadRequest, 239 | ), 240 | reqwest::StatusCode::INTERNAL_SERVER_ERROR => { 241 | JsonRpcServerError::InternalError { 242 | info: Some(String::from("Internal server error")), 243 | } 244 | } 245 | reqwest::StatusCode::SERVICE_UNAVAILABLE => { 246 | JsonRpcServerError::ResponseStatusError( 247 | JsonRpcServerResponseStatusError::ServiceUnavailable, 248 | ) 249 | } 250 | reqwest::StatusCode::REQUEST_TIMEOUT => { 251 | JsonRpcServerError::ResponseStatusError( 252 | JsonRpcServerResponseStatusError::TimeoutError, 253 | ) 254 | } 255 | unexpected => JsonRpcServerError::ResponseStatusError( 256 | JsonRpcServerResponseStatusError::Unexpected { status: unexpected }, 257 | ), 258 | })); 259 | } 260 | } 261 | let response_payload = response.bytes().await.map_err(|err| { 262 | JsonRpcError::TransportError(RpcTransportError::RecvError( 263 | JsonRpcTransportRecvError::PayloadRecvError(err), 264 | )) 265 | })?; 266 | let response_payload = serde_json::from_slice::(&response_payload); 267 | 268 | if let Ok(ref response_payload) = response_payload { 269 | log::debug!("response payload: {:#}", response_payload); 270 | } 271 | 272 | let response_message = near_jsonrpc_primitives::message::decoded_to_parsed( 273 | response_payload.and_then(serde_json::from_value), 274 | ) 275 | .map_err(|err| { 276 | JsonRpcError::TransportError(RpcTransportError::RecvError( 277 | JsonRpcTransportRecvError::PayloadParseError(err), 278 | )) 279 | })?; 280 | 281 | if let near_jsonrpc_primitives::message::Message::Response(response) = response_message { 282 | return M::parse_handler_response(response.result?) 283 | .map_err(|err| { 284 | JsonRpcError::TransportError(RpcTransportError::RecvError( 285 | JsonRpcTransportRecvError::ResponseParseError( 286 | JsonRpcTransportHandlerResponseError::ResultParseError(err), 287 | ), 288 | )) 289 | })? 290 | .map_err(|err| JsonRpcError::ServerError(JsonRpcServerError::HandlerError(err))); 291 | } 292 | Err(JsonRpcError::TransportError(RpcTransportError::RecvError( 293 | JsonRpcTransportRecvError::UnexpectedServerResponse(response_message), 294 | ))) 295 | } 296 | 297 | /// Add a header to this request. 298 | /// 299 | /// Depending on the header specified, this method either returns back 300 | /// the client, or a result containing the client. 301 | /// 302 | /// ### Example 303 | /// 304 | /// ``` 305 | /// use near_jsonrpc_client::JsonRpcClient; 306 | /// 307 | /// # #[tokio::main] 308 | /// # async fn main() -> Result<(), Box> { 309 | /// let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 310 | /// let client = client.header(("user-agent", "someclient/0.1.0"))?; // <- returns a result 311 | /// 312 | /// use near_jsonrpc_client::auth; 313 | /// 314 | /// let client = client.header( 315 | /// auth::ApiKey::new("cadc4c83-5566-4c94-aa36-773605150f44")?, // <- error handling here 316 | /// ); // <- returns the client 317 | /// # Ok(()) 318 | /// # } 319 | /// ``` 320 | pub fn header(self, entry: H) -> D::Output 321 | where 322 | H: header::HeaderEntry, 323 | D: header::HeaderEntryDiscriminant, 324 | { 325 | D::apply(self, entry) 326 | } 327 | 328 | /// Get a shared reference to the headers. 329 | pub fn headers(&self) -> &reqwest::header::HeaderMap { 330 | &self.headers 331 | } 332 | 333 | /// Get an exclusive reference to the headers. 334 | pub fn headers_mut(&mut self) -> &mut reqwest::header::HeaderMap { 335 | &mut self.headers 336 | } 337 | 338 | /// Manually create a new client connector. 339 | /// 340 | /// It's recommended to use the [`connect`](JsonRpcClient::connect) method instead as that method optimally 341 | /// reuses the default connector across invocations. 342 | /// 343 | /// However, if for some reason you still need to manually create a new connector, you can do so. 344 | /// Just remember to properly **reuse** it as much as possible. 345 | /// 346 | /// ## Example 347 | /// 348 | /// ``` 349 | /// # use near_jsonrpc_client::JsonRpcClient; 350 | /// let client_connector = JsonRpcClient::new_client(); 351 | /// 352 | /// let mainnet_client = client_connector.connect("https://rpc.mainnet.near.org"); 353 | /// let testnet_client = client_connector.connect("https://rpc.testnet.near.org"); 354 | /// ``` 355 | pub fn new_client() -> JsonRpcClientConnector { 356 | let mut headers = reqwest::header::HeaderMap::with_capacity(2); 357 | headers.insert( 358 | reqwest::header::CONTENT_TYPE, 359 | reqwest::header::HeaderValue::from_static("application/json"), 360 | ); 361 | 362 | log::debug!("initialized a new JSONRPC client connector"); 363 | JsonRpcClientConnector { 364 | client: reqwest::Client::builder() 365 | .default_headers(headers) 366 | .build() 367 | .unwrap(), 368 | } 369 | } 370 | 371 | /// Create a new client constructor using a custom web client. 372 | /// 373 | /// This is useful if you want to customize the `reqwest::Client` instance used by the JsonRpcClient. 374 | /// 375 | /// ## Example 376 | /// 377 | /// ``` 378 | /// use near_jsonrpc_client::JsonRpcClient; 379 | /// 380 | /// # #[tokio::main] 381 | /// # async fn main() -> Result<(), Box> { 382 | /// let web_client = reqwest::Client::builder() 383 | /// .proxy(reqwest::Proxy::all("https://192.168.1.1:4825")?) 384 | /// .build()?; 385 | /// 386 | /// let testnet_client = JsonRpcClient::with(web_client).connect("https://rpc.testnet.near.org"); 387 | /// # Ok(()) 388 | /// # } 389 | /// ``` 390 | pub fn with(client: reqwest::Client) -> JsonRpcClientConnector { 391 | JsonRpcClientConnector { client } 392 | } 393 | } 394 | 395 | impl fmt::Debug for JsonRpcClient { 396 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 397 | let mut builder = f.debug_struct("JsonRpcClient"); 398 | builder.field("server_addr", &self.inner.server_addr); 399 | builder.field("headers", &self.headers); 400 | builder.field("client", &self.inner.client); 401 | builder.finish() 402 | } 403 | } 404 | 405 | mod private { 406 | pub trait Sealed: ToString {} 407 | } 408 | 409 | pub trait AsUrl: private::Sealed {} 410 | 411 | impl private::Sealed for String {} 412 | 413 | impl AsUrl for String {} 414 | 415 | impl private::Sealed for &String {} 416 | 417 | impl AsUrl for &String {} 418 | 419 | impl private::Sealed for &str {} 420 | 421 | impl AsUrl for &str {} 422 | 423 | impl private::Sealed for reqwest::Url {} 424 | 425 | impl AsUrl for reqwest::Url {} 426 | 427 | #[cfg(test)] 428 | mod tests { 429 | use crate::{methods, JsonRpcClient}; 430 | 431 | #[tokio::test] 432 | async fn chk_status_testnet() { 433 | let client = JsonRpcClient::connect("https://rpc.testnet.near.org"); 434 | 435 | let status = client.call(methods::status::RpcStatusRequest).await; 436 | 437 | assert!( 438 | matches!(status, Ok(methods::status::RpcStatusResponse { .. })), 439 | "expected an Ok(RpcStatusResponse), found [{:?}]", 440 | status 441 | ); 442 | } 443 | 444 | #[tokio::test] 445 | #[cfg(feature = "any")] 446 | async fn any_typed_ok() -> Result<(), Box> { 447 | let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 448 | 449 | let tx_status = client 450 | .call(methods::any::( 451 | "tx", 452 | serde_json::json!([ 453 | "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U", 454 | "miraclx.near", 455 | ]), 456 | )) 457 | .await; 458 | 459 | assert!( 460 | matches!( 461 | tx_status, 462 | Ok(methods::tx::RpcTransactionResponse { ref final_execution_outcome, .. }) 463 | if final_execution_outcome.clone().unwrap().into_outcome().transaction.signer_id == "miraclx.near" 464 | && final_execution_outcome.clone().unwrap().into_outcome().transaction.hash == "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U".parse()? 465 | ), 466 | "expected an Ok(RpcTransactionStatusResponse) with matching signer_id + hash, found [{:?}]", 467 | tx_status 468 | ); 469 | 470 | Ok(()) 471 | } 472 | 473 | #[tokio::test] 474 | #[cfg(feature = "any")] 475 | async fn any_typed_err() -> Result<(), Box> { 476 | let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 477 | 478 | let tx_error = client 479 | .call(methods::any::( 480 | "tx", 481 | serde_json::json!([ 482 | "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8D", 483 | "youser.near", 484 | ]), 485 | )) 486 | .await 487 | .expect_err("request must not succeed"); 488 | 489 | assert!( 490 | matches!( 491 | tx_error.handler_error(), 492 | Some(methods::tx::RpcTransactionError::UnknownTransaction { 493 | requested_transaction_hash 494 | }) 495 | if requested_transaction_hash.to_string() == "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8D" 496 | ), 497 | "expected an Ok(RpcTransactionError::UnknownTransaction) with matching hash, found [{:?}]", 498 | tx_error 499 | ); 500 | 501 | Ok(()) 502 | } 503 | 504 | #[tokio::test] 505 | #[cfg(feature = "any")] 506 | async fn any_untyped_ok() { 507 | let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 508 | 509 | let status = client 510 | .call( 511 | methods::any::>( 512 | "tx", 513 | serde_json::json!([ 514 | "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U", 515 | "miraclx.near", 516 | ]), 517 | ), 518 | ) 519 | .await 520 | .expect("request must not fail"); 521 | 522 | assert_eq!( 523 | status["transaction"]["signer_id"], "miraclx.near", 524 | "expected a tx_status with matching signer_id, [{:#}]", 525 | status 526 | ); 527 | assert_eq!( 528 | status["transaction"]["hash"], "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U", 529 | "expected a tx_status with matching hash, [{:#}]", 530 | status 531 | ); 532 | } 533 | 534 | #[tokio::test] 535 | #[cfg(feature = "any")] 536 | async fn any_untyped_err() { 537 | let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com"); 538 | 539 | let tx_error = client 540 | .call( 541 | methods::any::>( 542 | "tx", 543 | serde_json::json!([ 544 | "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8D", 545 | "youser.near", 546 | ]), 547 | ), 548 | ) 549 | .await 550 | .expect_err("request must not succeed"); 551 | let tx_error = tx_error 552 | .handler_error() 553 | .expect("expected a handler error from query request"); 554 | 555 | assert_eq!( 556 | tx_error["info"]["requested_transaction_hash"], 557 | "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8D", 558 | "expected an error with matching hash, [{:#}]", 559 | tx_error 560 | ); 561 | assert_eq!( 562 | tx_error["name"], "UNKNOWN_TRANSACTION", 563 | "expected an UnknownTransaction, [{:#}]", 564 | tx_error 565 | ); 566 | } 567 | } 568 | --------------------------------------------------------------------------------