├── src ├── types.rs ├── lib.rs └── provider.rs ├── .gitignore ├── Cargo.toml ├── .github └── workflows │ └── rust.yml ├── LICENSE-MIT ├── README.md ├── LICENSE-APACHE └── tests └── provider.rs /src/types.rs: -------------------------------------------------------------------------------- 1 | pub use primitive_types::U256; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | ///## 🚀 Quick start 2 | ///```rust 3 | ///use ethrs::provider::Provider; 4 | ///use ethrs::provider::Block; 5 | ///use ethrs::provider::DefaultBlockParam; 6 | ///use std::error::Error; 7 | 8 | ///fn main() -> Result<(), Box> { 9 | /// let provider = Provider::new("https://rpc.sepolia.org"); 10 | /// // Get the latest block number 11 | /// print!("Latest block number: {}", provider.block_number().unwrap()); 12 | /// // Or fetch a pending block 13 | /// let pending_block: Block = provider.get_block_by_number(Some(DefaultBlockParam::PENDING), None)?.unwrap(); 14 | /// // More APIs available in the docs! 15 | /// Ok(()) 16 | ///} 17 | ///``` 18 | pub mod provider; 19 | pub mod types; 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ethrs" 3 | version = "0.1.1" 4 | edition = "2021" 5 | authors = ["QEDK "] 6 | description = "An opinionated and fast library for interaction with the EVM" 7 | homepage = "https://github.com/QEDK/ethrs" 8 | repository = "https://github.com/QEDK/ethrs" 9 | documentation = "https://docs.rs/ethrs" 10 | readme = "README.md" 11 | license = "MIT OR Apache-2.0" 12 | keywords = ["evm", "web3"] 13 | categories = ["web-programming", "api-bindings", "cryptography::cryptocurrencies"] 14 | rust-version = "1.67.0" 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [dependencies] 19 | reqwest = { version = "0.11", features = ["blocking", "json"] } 20 | serde = { version = "1.0", features = ["derive"] } 21 | regex = "1" 22 | lazy_static = "1.4" 23 | primitive-types = { version = "0.12.1", features = ["impl-serde"], default-features = false } 24 | serde_json = "1.0.96" 25 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | ci: 14 | runs-on: ubuntu-latest 15 | continue-on-error: ${{ matrix.rust == 'nightly' }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | rust: 20 | - stable 21 | - beta 22 | - nightly 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | 27 | - uses: dtolnay/rust-toolchain@stable 28 | with: 29 | toolchain: ${{ matrix.rust }} 30 | components: rustfmt, clippy 31 | 32 | - run: cargo build --release --all-features -v 33 | - run: cargo test --all-features -v 34 | - run: cargo fmt --all -- --check 35 | - run: cargo clippy 36 | 37 | - name: Security audit 38 | uses: actions-rs/audit-check@v1 39 | with: 40 | token: ${{ secrets.GITHUB_TOKEN }} 41 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 QEDK 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 all 11 | 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 THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ethrs [![Rust CI](https://github.com/QEDK/ethrs/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/QEDK/ethrs/actions/workflows/rust.yml) 2 | An opinionated and blazing-fast crate for interacting with the EVM ⚡️ 3 | This crate tries to simplify the work involved with serializing and deserializing, mostly choosing to default to `String`, `U256`, and `u128` types. The choice is intentional and prevents assumptions regarding deserialized data. 4 | 5 | ⚠️ ***This crate is still in `beta` and will not follow semver until a production release. It is recommended that you pin the crate when using it to ensure that non-backward compatible changes do not affect you.*** 6 | 7 | ### 🧰 Installation 8 | You can install this crate easily via `cargo` by running the command: 9 | ```bash 10 | cargo add ethrs 11 | ``` 12 | or, add it manually in your `Cargo.toml` file like: 13 | ```TOML 14 | [dependencies] 15 | ethrs = "0.1.1" 16 | ``` 17 | 18 | ## 🚀 Quick start 19 | ```rust 20 | use ethrs::provider::Provider; 21 | use ethrs::provider::Block; 22 | use ethrs::provider::DefaultBlockParam; 23 | use std::error::Error; 24 | 25 | fn main() -> Result<(), Box> { 26 | let provider = Provider::new("https://rpc.ankr.com/eth"); 27 | // Get the latest block number 28 | print!("Latest block number: {}", provider.block_number().unwrap()); 29 | // Or fetch a pending block 30 | let pending_block: Block = provider.get_block_by_number(Some(DefaultBlockParam::PENDING), None)?.unwrap(); 31 | // More APIs available in the docs! 32 | Ok(()) 33 | } 34 | ``` 35 | 36 | ## 📜 License 37 | 38 | Licensed under either of: 39 | 40 | * MIT License ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 41 | * Apache-2.0 License ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) 42 | 43 | at your option. 44 | 45 | ### ✏ Contribution 46 | 47 | Unless you explicitly state otherwise, any contribution intentionally submitted 48 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 49 | -------------------------------------------------------------------------------- /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 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /tests/provider.rs: -------------------------------------------------------------------------------- 1 | use ethrs::provider::Provider; 2 | use ethrs::provider::{CallInput, DefaultBlockParam, TransactionInput}; 3 | use ethrs::types::U256; 4 | 5 | use lazy_static::lazy_static; 6 | use std::error::Error; 7 | 8 | lazy_static! { 9 | static ref PROVIDER: Provider = Provider::new("https://rpc.sepolia.org"); 10 | } 11 | 12 | #[test] 13 | fn test_block_number() -> Result<(), Box> { 14 | assert!(PROVIDER.block_number().unwrap() > 3347000); 15 | Ok(()) 16 | } 17 | 18 | #[test] 19 | fn test_gas_price() -> Result<(), Box> { 20 | assert!(PROVIDER.gas_price().unwrap() >= 8); 21 | Ok(()) 22 | } 23 | 24 | #[test] 25 | fn test_get_balance() -> Result<(), Box> { 26 | assert!( 27 | PROVIDER 28 | .get_balance("0x0000000000000000000000000000000000000000", None, None) 29 | .unwrap() 30 | > 0 31 | ); 32 | PROVIDER 33 | .get_balance( 34 | "0x0000000000000000000000000000000000000000", 35 | Some(DefaultBlockParam::EARLIEST), 36 | None, 37 | ) 38 | .unwrap(); 39 | assert!( 40 | PROVIDER 41 | .get_balance( 42 | "0x0000000000000000000000000000000000000000", 43 | Some(DefaultBlockParam::LATEST), 44 | None, 45 | ) 46 | .unwrap() 47 | > 0 48 | ); 49 | assert!( 50 | PROVIDER 51 | .get_balance( 52 | "0x0000000000000000000000000000000000000000", 53 | Some(DefaultBlockParam::PENDING), 54 | None, 55 | ) 56 | .unwrap() 57 | > 0 58 | ); 59 | assert!( 60 | PROVIDER 61 | .get_balance( 62 | "0x0000000000000000000000000000000000000000", 63 | None, 64 | Some(PROVIDER.block_number().unwrap() - 1), 65 | ) 66 | .unwrap() 67 | > 0 68 | ); 69 | Ok(()) 70 | } 71 | 72 | #[test] 73 | fn test_get_storage_at() -> Result<(), Box> { 74 | assert_eq!( 75 | PROVIDER 76 | .get_storage_at( 77 | "0x0000000000000000000000000000000000000000", 78 | "0x0", 79 | None, 80 | None 81 | ) 82 | .unwrap(), 83 | "0x0000000000000000000000000000000000000000000000000000000000000000" 84 | ); 85 | assert_eq!( 86 | PROVIDER 87 | .get_storage_at( 88 | "0x95ab1853c803c740e7b095776b217f0e8cbd2e16", 89 | "0x0", 90 | None, 91 | None 92 | ) 93 | .unwrap(), 94 | "0x0000000000000000000000da9e8e71bb750a996af33ebb8abb18cd9eb9dc7500" 95 | ); 96 | Ok(()) 97 | } 98 | 99 | #[test] 100 | fn test_get_transaction_count() -> Result<(), Box> { 101 | assert!( 102 | PROVIDER 103 | .get_transaction_count("0x0000000000000000000000000000000000000000", None, None) 104 | .unwrap() 105 | == 0 106 | ); 107 | assert!( 108 | PROVIDER 109 | .get_transaction_count( 110 | "0x0000000000000000000000000000000000000000", 111 | Some(DefaultBlockParam::EARLIEST), 112 | None 113 | ) 114 | .unwrap() 115 | == 0 116 | ); 117 | assert!( 118 | PROVIDER 119 | .get_transaction_count( 120 | "0x0000000000000000000000000000000000000000", 121 | Some(DefaultBlockParam::LATEST), 122 | None 123 | ) 124 | .unwrap() 125 | == 0 126 | ); 127 | assert!( 128 | PROVIDER 129 | .get_transaction_count( 130 | "0x0000000000000000000000000000000000000000", 131 | Some(DefaultBlockParam::PENDING), 132 | None 133 | ) 134 | .unwrap() 135 | == 0 136 | ); 137 | assert!( 138 | PROVIDER 139 | .get_transaction_count( 140 | "0x0000000000000000000000000000000000000000", 141 | None, 142 | Some(PROVIDER.block_number().unwrap() - 1) 143 | ) 144 | .unwrap() 145 | == 0 146 | ); 147 | Ok(()) 148 | } 149 | 150 | #[test] 151 | fn test_get_block_transaction_count_by_hash() -> Result<(), Box> { 152 | assert!( 153 | PROVIDER.get_block_transaction_count_by_hash( 154 | "0x6c4925c897c45d377d8fb3ef59df7e0cf97604fc85b909bb806818368fdc6b07" 155 | )? == Some(5) 156 | ); 157 | assert!(PROVIDER 158 | .get_block_transaction_count_by_hash( 159 | "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 160 | )? 161 | .is_none()); 162 | assert!( 163 | PROVIDER.get_block_transaction_count_by_hash( 164 | "0x68a52ca2491ab61f32d046021654b65859db15bd763a4e09f8ca0e923de707cd" 165 | )? == Some(0) 166 | ); 167 | Ok(()) 168 | } 169 | 170 | #[test] 171 | fn test_get_block_by_hash() -> Result<(), Box> { 172 | assert!(PROVIDER 173 | .get_block_by_hash("0x14c2bae040612f036c032f7f0eccf9b3389cd8c30d810df69abdf772f7acf6d8")? 174 | .is_some()); 175 | assert!(PROVIDER 176 | .get_block_by_hash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")? 177 | .is_none()); 178 | assert!(PROVIDER 179 | .get_block_by_hash_with_tx( 180 | "0x33ddfd6eebe80ec8fe2fecfd8fbd7fa7abd5ceb8f53ec11dff1e90312c2828b5" 181 | )? 182 | .is_some()); 183 | assert!(PROVIDER 184 | .get_block_by_hash_with_tx( 185 | "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 186 | )? 187 | .is_none()); 188 | Ok(()) 189 | } 190 | 191 | #[test] 192 | fn test_get_block_by_number() -> Result<(), Box> { 193 | assert!(PROVIDER.get_block_by_number(None, None)?.is_some()); 194 | assert!(PROVIDER 195 | .get_block_by_number(Some(DefaultBlockParam::EARLIEST), None)? 196 | .is_some()); 197 | assert!(PROVIDER 198 | .get_block_by_number(Some(DefaultBlockParam::LATEST), None)? 199 | .is_some()); 200 | assert!(PROVIDER 201 | .get_block_by_number(Some(DefaultBlockParam::PENDING), None)? 202 | .is_some()); 203 | assert!(PROVIDER 204 | .get_block_by_number(None, Some(PROVIDER.block_number().unwrap()))? 205 | .is_some()); 206 | assert!(PROVIDER 207 | .get_block_by_number(None, Some(9999999999))? 208 | .is_none()); 209 | assert!(PROVIDER.get_block_by_number_with_tx(None, None)?.is_some()); 210 | assert!(PROVIDER 211 | .get_block_by_number_with_tx(Some(DefaultBlockParam::EARLIEST), None)? 212 | .is_some()); 213 | assert!(PROVIDER 214 | .get_block_by_number_with_tx(Some(DefaultBlockParam::LATEST), None)? 215 | .is_some()); 216 | assert!(PROVIDER 217 | .get_block_by_number_with_tx(Some(DefaultBlockParam::PENDING), None)? 218 | .is_some()); 219 | assert!(PROVIDER 220 | .get_block_by_number_with_tx(None, Some(PROVIDER.block_number().unwrap()))? 221 | .is_some()); 222 | assert!(PROVIDER 223 | .get_block_by_number_with_tx(None, Some(9999999999))? 224 | .is_none()); 225 | Ok(()) 226 | } 227 | 228 | #[test] 229 | fn test_get_code() -> Result<(), Box> { 230 | assert_eq!( 231 | PROVIDER 232 | .get_code("0x0000000000000000000000000000000000000000", None, None) 233 | .unwrap(), 234 | "0x".to_owned() 235 | ); 236 | assert_eq!( 237 | PROVIDER 238 | .get_code( 239 | "0x0000000000000000000000000000000000000000", 240 | Some(DefaultBlockParam::EARLIEST), 241 | None, 242 | ) 243 | .unwrap(), 244 | "0x".to_owned() 245 | ); 246 | assert_eq!( 247 | PROVIDER 248 | .get_code( 249 | "0x0000000000000000000000000000000000000000", 250 | Some(DefaultBlockParam::LATEST), 251 | None, 252 | ) 253 | .unwrap(), 254 | "0x".to_owned() 255 | ); 256 | assert_eq!( 257 | PROVIDER 258 | .get_code( 259 | "0x0000000000000000000000000000000000000000", 260 | Some(DefaultBlockParam::PENDING), 261 | None, 262 | ) 263 | .unwrap(), 264 | "0x".to_owned() 265 | ); 266 | assert_eq!( 267 | PROVIDER 268 | .get_code( 269 | "0x0000000000000000000000000000000000000000", 270 | Some(DefaultBlockParam::FINALIZED), 271 | None, 272 | ) 273 | .unwrap(), 274 | "0x".to_owned() 275 | ); 276 | assert_eq!( 277 | PROVIDER 278 | .get_code( 279 | "0x0000000000000000000000000000000000000000", 280 | None, 281 | Some(PROVIDER.block_number().unwrap() - 1), 282 | ) 283 | .unwrap(), 284 | "0x".to_owned() 285 | ); 286 | assert_eq!(PROVIDER.get_code("0x790830c1eaab862fd35dbce2e7ea1aebce32fce3", None, None).unwrap(), "0x6060604052600436106100ae5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde0381146100b8578063095ea7b31461014257806318160ddd1461017857806323b872dd1461019d5780632e1a7d4d146101c5578063313ce567146101db57806370a082311461020457806395d89b4114610223578063a9059cbb14610236578063d0e30db0146100ae578063dd62ed3e14610258575b6100b661027d565b005b34156100c357600080fd5b6100cb6102d3565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156101075780820151838201526020016100ef565b50505050905090810190601f1680156101345780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561014d57600080fd5b610164600160a060020a0360043516602435610371565b604051901515815260200160405180910390f35b341561018357600080fd5b61018b6103dd565b60405190815260200160405180910390f35b34156101a857600080fd5b610164600160a060020a03600435811690602435166044356103eb565b34156101d057600080fd5b6100b6600435610531565b34156101e657600080fd5b6101ee6105df565b60405160ff909116815260200160405180910390f35b341561020f57600080fd5b61018b600160a060020a03600435166105e8565b341561022e57600080fd5b6100cb6105fa565b341561024157600080fd5b610164600160a060020a0360043516602435610665565b341561026357600080fd5b61018b600160a060020a0360043581169060243516610679565b600160a060020a033316600081815260036020526040908190208054349081019091557fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c915190815260200160405180910390a2565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103695780601f1061033e57610100808354040283529160200191610369565b820191906000526020600020905b81548152906001019060200180831161034c57829003601f168201915b505050505081565b600160a060020a03338116600081815260046020908152604080832094871680845294909152808220859055909291907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259085905190815260200160405180910390a350600192915050565b600160a060020a0330163190565b600160a060020a0383166000908152600360205260408120548290101561041157600080fd5b33600160a060020a031684600160a060020a03161415801561045b5750600160a060020a038085166000908152600460209081526040808320339094168352929052205460001914155b156104c257600160a060020a03808516600090815260046020908152604080832033909416835292905220548290101561049457600080fd5b600160a060020a03808516600090815260046020908152604080832033909416835292905220805483900390555b600160a060020a038085166000818152600360205260408082208054879003905592861680825290839020805486019055917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9085905190815260200160405180910390a35060019392505050565b600160a060020a0333166000908152600360205260409020548190101561055757600080fd5b600160a060020a033316600081815260036020526040908190208054849003905582156108fc0290839051600060405180830381858888f19350505050151561059f57600080fd5b33600160a060020a03167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b658260405190815260200160405180910390a250565b60025460ff1681565b60036020526000908152604090205481565b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103695780601f1061033e57610100808354040283529160200191610369565b60006106723384846103eb565b9392505050565b6004602090815260009283526040808420909152908252902054815600a165627a7a72305820976c9c45a8c1e47424c3304cee5b065aefb0c6539e9fb6b31dc3eee2abf17f650029"); 287 | Ok(()) 288 | } 289 | 290 | #[test] 291 | fn test_get_transaction_by_hash() -> Result<(), Box> { 292 | assert!(PROVIDER 293 | .get_transaction_by_hash( 294 | "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 295 | )? 296 | .is_none()); 297 | assert!(PROVIDER 298 | .get_transaction_by_hash( 299 | "0xefdd363eae1829b4e57bd7e19975adfe471b8639b4ffa1b5ce511b7960525b79" 300 | )? 301 | .is_some()); 302 | Ok(()) 303 | } 304 | 305 | #[test] 306 | fn test_get_transaction_by_block_hash_and_index() -> Result<(), Box> { 307 | assert!(PROVIDER 308 | .get_transaction_by_block_hash_and_index( 309 | "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 310 | U256::from(1) 311 | )? 312 | .is_none()); 313 | assert!(PROVIDER 314 | .get_transaction_by_block_hash_and_index( 315 | "0x4938120f0baffd265200d757b6da74e1d80e0a82ff0ed3d7eb3277613ce6f4a4", 316 | U256::from(1) 317 | )? 318 | .is_some()); 319 | assert!(PROVIDER 320 | .get_transaction_by_block_number_and_index(U256::from("0x7FFFFFFFFFFFFFFF"), U256::from(1))? 321 | .is_none()); 322 | Ok(()) 323 | } 324 | 325 | #[test] 326 | fn test_get_transaction_by_block_number_and_index() -> Result<(), Box> { 327 | PROVIDER 328 | .get_transaction_by_block_number_and_index( 329 | U256::from(PROVIDER.block_number().unwrap()), 330 | U256::from(1), 331 | ) 332 | .unwrap(); // some blocks may have no transactions 333 | assert!(PROVIDER 334 | .get_transaction_by_block_number_and_index(U256::from(2893700), U256::from(1))? 335 | .is_some()); 336 | assert!(PROVIDER 337 | .get_transaction_by_block_number_and_index(U256::from("0x7FFFFFFFFFFFFFFF"), U256::from(1))? 338 | .is_none()); 339 | Ok(()) 340 | } 341 | 342 | #[test] 343 | fn test_get_transaction_receipt() -> Result<(), Box> { 344 | assert!(PROVIDER 345 | .get_transaction_receipt( 346 | "0x10e8caafb752c4b611c51dfa784168eebbf1b2819523ea6e8cdf7452552ef6c3" 347 | )? 348 | .is_some()); 349 | assert!(PROVIDER 350 | .get_transaction_receipt( 351 | "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 352 | )? 353 | .is_none()); 354 | Ok(()) 355 | } 356 | 357 | #[test] 358 | #[should_panic(expected = "unknown account")] 359 | fn test_send_transaction() { 360 | let tx = TransactionInput { 361 | from: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_owned(), 362 | to: Some("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_owned()), 363 | gas: Some(U256::from(21000)), 364 | gas_price: Some(U256::from(1)), 365 | value: Some(U256::from(1)), 366 | data: Some("0xFF".to_owned()), 367 | nonce: Some(U256::from(0)), 368 | }; 369 | // this will panic since public RPC has no unlocked account 370 | PROVIDER.send_transaction(tx).unwrap(); 371 | } 372 | 373 | #[test] 374 | fn test_call() -> Result<(), Box> { 375 | let mut tx = CallInput { 376 | from: None, 377 | to: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_owned(), 378 | gas: None, 379 | gas_price: None, 380 | value: None, 381 | data: None, 382 | }; 383 | assert_eq!(PROVIDER.call(tx.clone(), None, None)?, "0x".to_owned()); 384 | assert_eq!( 385 | PROVIDER.call(tx.clone(), Some(DefaultBlockParam::PENDING), None)?, 386 | "0x".to_owned() 387 | ); 388 | assert_eq!( 389 | PROVIDER.call(tx.clone(), Some(DefaultBlockParam::SAFE), None)?, 390 | "0x".to_owned() 391 | ); 392 | assert_eq!( 393 | PROVIDER.call(tx.clone(), Some(DefaultBlockParam::FINALIZED), None)?, 394 | "0x".to_owned() 395 | ); 396 | assert_eq!( 397 | PROVIDER.call(tx.clone(), Some(DefaultBlockParam::EARLIEST), None)?, 398 | "0x".to_owned() 399 | ); 400 | assert_eq!( 401 | PROVIDER.call(tx.clone(), None, Some(PROVIDER.block_number().unwrap() - 1))?, 402 | "0x".to_owned() 403 | ); 404 | tx = CallInput { 405 | from: None, 406 | to: "0xdeceabcc2896ac5a6c4c45703087844c67ecf0a0".to_owned(), 407 | gas: None, 408 | gas_price: None, 409 | value: None, 410 | data: Some("0xd800df5c".to_owned()), 411 | }; 412 | assert_eq!( 413 | PROVIDER.call(tx, None, None)?, 414 | "0x00000000000000000000000000000000000000000000000000000000000003e8".to_owned() 415 | ); 416 | Ok(()) 417 | } 418 | -------------------------------------------------------------------------------- /src/provider.rs: -------------------------------------------------------------------------------- 1 | //!The provider module provides all the APIs necessary to interact with EVM JSON-RPC nodes. The most important of which is the `Provider` struct. 2 | //!See the [implementation](https://docs.rs/ethrs/*/ethrs/provider/struct.Provider.html) documentation for more details. 3 | use lazy_static::lazy_static; 4 | use primitive_types::U256; 5 | use regex::Regex; 6 | use reqwest; 7 | use reqwest::header::{HeaderMap, CONTENT_TYPE}; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | use std::error::Error; 11 | use std::fmt::Write; 12 | use std::string::String; 13 | 14 | ///The `Provider` struct simply contains the RPC url, a `reqwest` client and default headers. 15 | ///## Example 16 | ///```rust 17 | ///use ethrs::provider::Provider; 18 | /// 19 | ///let provider = Provider::new("https://rpc.ankr.com/eth"); 20 | ///``` 21 | #[derive(Debug, Clone, Default)] 22 | #[non_exhaustive] 23 | pub struct Provider { 24 | url: String, 25 | client: reqwest::blocking::Client, 26 | headers: HeaderMap, 27 | } 28 | 29 | pub enum DefaultBlockParam { 30 | EARLIEST, 31 | FINALIZED, 32 | SAFE, 33 | LATEST, 34 | PENDING, 35 | } 36 | 37 | ///The `RPCResponse` struct allows for deserialization of generic RPC requests that may either return an error or a single hash as a result. 38 | #[derive(Deserialize, Debug)] 39 | pub struct RPCResponse { 40 | error: Option, 41 | result: Option, 42 | } 43 | 44 | #[derive(Deserialize, Debug)] 45 | pub struct RPCError { 46 | message: String, 47 | } 48 | 49 | ///The `BlockRPCResponse` struct allows for deserialization of JSON-RPC requests that may either return an error or return a block as a result. 50 | #[derive(Deserialize, Debug)] 51 | pub struct BlockRPCResponse { 52 | error: Option, 53 | result: Option, 54 | } 55 | 56 | ///The `TxRPCResponse` struct allows for deserialization of JSON-RPC requests that may either return an error or return a transaction as a result. 57 | #[derive(Deserialize, Debug)] 58 | pub struct TxRPCResponse { 59 | error: Option, 60 | result: Option, 61 | } 62 | 63 | ///The `TxReceiptRPCResponse` struct allows for deserialization of JSON-RPC requests that may either return an error or return a transaction receipt as a result. 64 | #[derive(Deserialize, Debug)] 65 | pub struct TxReceiptRPCResponse { 66 | error: Option, 67 | result: Option, 68 | } 69 | 70 | ///The `BlockWithTxRPCResponse` struct allows for deserialization of JSON-RPC requests that may either return an error or return a block with transactions as a result. 71 | #[derive(Deserialize, Debug)] 72 | pub struct BlockWithTxRPCResponse { 73 | error: Option, 74 | result: Option, 75 | } 76 | 77 | ///The `Block` struct allows for returning successfully deserialized blocks from JSON-RPC requests. 78 | ///## Example 79 | ///```rust 80 | ///use ethrs::provider::Provider; 81 | ///use std::error::Error; 82 | /// 83 | ///fn main() -> Result<(), Box> { 84 | /// let provider = Provider::new("https://rpc.sepolia.org"); 85 | /// assert!(provider 86 | /// .get_block_by_number( 87 | /// None, None, 88 | /// )? 89 | /// .is_some()); 90 | /// Ok(()) 91 | ///} 92 | ///``` 93 | #[derive(Debug, Deserialize)] 94 | #[serde(rename_all = "camelCase")] 95 | pub struct Block { 96 | pub number: Option, 97 | pub hash: Option, 98 | pub parent_hash: String, 99 | pub nonce: Option, 100 | pub sha3_uncles: String, 101 | pub logs_bloom: Option, 102 | pub transactions_root: String, 103 | pub state_root: String, 104 | pub receipts_root: String, 105 | pub miner: Option, 106 | pub difficulty: U256, 107 | pub total_difficulty: Option, 108 | pub extra_data: String, 109 | pub size: U256, 110 | pub gas_limit: U256, 111 | pub gas_used: U256, 112 | pub timestamp: U256, 113 | pub transactions: Vec, 114 | pub uncles: Vec, 115 | } 116 | 117 | ///The `Block` struct allows for returning successfully deserialized blocks with transactions from JSON-RPC requests. 118 | ///## Example 119 | ///```rust 120 | ///use ethrs::provider::Provider; 121 | ///use std::error::Error; 122 | /// 123 | ///fn main() -> Result<(), Box> { 124 | /// let provider = Provider::new("https://rpc.sepolia.org"); 125 | /// assert!(provider 126 | /// .get_block_by_number_with_tx( 127 | /// None, None, 128 | /// )? 129 | /// .is_some()); 130 | /// Ok(()) 131 | ///} 132 | ///``` 133 | #[derive(Debug, Deserialize)] 134 | #[serde(rename_all = "camelCase")] 135 | pub struct BlockWithTx { 136 | pub number: Option, 137 | pub hash: Option, 138 | pub parent_hash: String, 139 | pub nonce: Option, 140 | pub sha3_uncles: String, 141 | pub logs_bloom: Option, 142 | pub transactions_root: String, 143 | pub state_root: String, 144 | pub receipts_root: String, 145 | pub miner: Option, 146 | pub difficulty: U256, 147 | pub total_difficulty: Option, 148 | pub extra_data: String, 149 | pub size: U256, 150 | pub gas_limit: U256, 151 | pub gas_used: U256, 152 | pub timestamp: U256, 153 | pub transactions: Vec, 154 | pub uncles: Vec, 155 | } 156 | 157 | ///The `Transaction` struct allows for returning successfully deserialized transactions from JSON-RPC requests. 158 | ///## Example 159 | ///```rust 160 | ///use ethrs::provider::Provider; 161 | ///use std::error::Error; 162 | /// 163 | ///fn main() -> Result<(), Box> { 164 | /// let provider = Provider::new("https://rpc.sepolia.org"); 165 | /// assert!(provider 166 | /// .get_transaction_by_hash( 167 | /// "0x6648b858a3d2b716d4c05c5d611844eb9827e2eea5bfc9db7a92187afd4d8c17" 168 | /// )? 169 | /// .is_some()); 170 | /// Ok(()) 171 | ///} 172 | ///``` 173 | #[derive(Debug, Deserialize)] 174 | #[serde(rename_all = "camelCase")] 175 | pub struct Transaction { 176 | pub block_hash: Option, 177 | pub block_number: Option, 178 | pub from: String, 179 | pub gas: U256, 180 | pub gas_price: U256, 181 | pub hash: String, 182 | pub input: String, 183 | pub nonce: U256, 184 | pub to: Option, 185 | pub transaction_index: Option, 186 | pub value: U256, 187 | pub v: String, 188 | pub r: String, 189 | pub s: String, 190 | } 191 | 192 | #[derive(Debug, Deserialize)] 193 | #[serde(rename_all = "camelCase")] 194 | pub struct TransactionReceipt { 195 | pub transaction_hash: String, 196 | pub transaction_index: U256, 197 | pub block_hash: String, 198 | pub block_number: U256, 199 | pub from: String, 200 | pub to: Option, 201 | pub cumulative_gas_used: U256, 202 | pub effective_gas_price: U256, 203 | pub gas_used: U256, 204 | pub contract_address: Option, 205 | pub logs: Vec, 206 | pub logs_bloom: String, 207 | pub status: Option, 208 | pub root: Option, 209 | } 210 | 211 | #[derive(Debug, Serialize, Clone)] 212 | #[serde(rename_all = "camelCase")] 213 | pub struct TransactionInput { 214 | pub from: String, 215 | pub to: Option, 216 | pub gas: Option, 217 | pub gas_price: Option, 218 | pub value: Option, 219 | pub data: Option, 220 | pub nonce: Option, 221 | } 222 | 223 | #[derive(Debug, Serialize, Clone)] 224 | #[serde(rename_all = "camelCase")] 225 | pub struct CallInput { 226 | pub from: Option, 227 | pub to: String, 228 | pub gas: Option, 229 | pub gas_price: Option, 230 | pub value: Option, 231 | pub data: Option, 232 | } 233 | 234 | #[derive(Debug, Deserialize)] 235 | #[serde(rename_all = "camelCase")] 236 | pub struct Log { 237 | pub removed: bool, 238 | pub log_index: U256, 239 | pub transaction_index: U256, 240 | pub transaction_hash: String, 241 | pub block_hash: String, 242 | pub block_number: U256, 243 | pub address: String, 244 | pub data: String, 245 | pub topics: Vec, 246 | } 247 | 248 | lazy_static! { 249 | static ref ADDRESS_REGEX: Regex = Regex::new(r"0x[0-9A-Fa-f]{40}").unwrap(); 250 | static ref BLOCKHASH_REGEX: Regex = Regex::new(r"0x[0-9A-Fa-f]{64}").unwrap(); 251 | static ref SLOT_REGEX: Regex = Regex::new(r"0x[0-9A-Fa-f]{1,64}").unwrap(); 252 | } 253 | 254 | ///The `Provider` module requires an HTTP(S) JSON-RPC URL and is responsible for handling all your JSON-RPC requests. 255 | ///## Example 256 | ///```rust 257 | ///use ethrs::provider::Provider; 258 | /// 259 | ///let provider = Provider::new("https://rpc.sepolia.org"); 260 | ///``` 261 | impl Provider { 262 | ///The `Provider::new()` associated function takes an HTTP(S) JSON-RPC URL and returns a `Provider` 263 | ///instance to make your requests. 264 | ///## Example 265 | ///```rust 266 | ///use ethrs::provider::Provider; 267 | /// 268 | ///let provider: Provider = Provider::new("https://rpc.sepolia.org"); 269 | ///``` 270 | pub fn new(_url: &str) -> Provider { 271 | let mut headers: HeaderMap = HeaderMap::new(); 272 | headers.insert(CONTENT_TYPE, "application/json".parse().unwrap()); 273 | Provider { 274 | url: _url.to_owned(), 275 | client: reqwest::blocking::Client::new(), 276 | headers: headers.clone(), 277 | } 278 | } 279 | 280 | ///The `gas_price()` function attempts to return the current block number as `Ok(u128)`. Returns an `Err()` on JSON-RPC errors. 281 | ///## Example 282 | ///```rust 283 | ///use ethrs::provider::Provider; 284 | ///use ethrs::types::U256; 285 | ///use std::error::Error; 286 | /// 287 | ///fn main() -> Result<(), Box> { 288 | /// let provider = Provider::new("https://rpc.sepolia.org"); 289 | /// assert!(provider 290 | /// .block_number()? 291 | /// >= 2900000); 292 | /// Ok(()) 293 | ///} 294 | ///``` 295 | pub fn block_number(&self) -> Result> { 296 | let json: RPCResponse = self 297 | .client 298 | .post(&self.url) 299 | .body("{\"method\":\"eth_blockNumber\",\"params\":[],\"id\":1,\"jsonrpc\":\"2.0\"}") 300 | .headers(self.headers.clone()) 301 | .send()? 302 | .json()?; 303 | 304 | match json.error { 305 | Some(err) => Err(err.message.into()), 306 | None => Ok(u128::from_str_radix( 307 | json.result.unwrap().strip_prefix("0x").unwrap(), 308 | 16, 309 | )?), 310 | } 311 | } 312 | 313 | ///The `gas_price()` function attempts to return the current gas price as `Ok(u128)`. Returns an `Err()` on JSON-RPC errors. 314 | ///## Example 315 | ///```rust 316 | ///use ethrs::provider::Provider; 317 | ///use ethrs::types::U256; 318 | ///use std::error::Error; 319 | /// 320 | ///fn main() -> Result<(), Box> { 321 | /// let provider = Provider::new("https://rpc.sepolia.org"); 322 | /// assert!(provider 323 | /// .gas_price()? 324 | /// >= 7); 325 | /// Ok(()) 326 | ///} 327 | ///``` 328 | pub fn gas_price(&self) -> Result> { 329 | let json: RPCResponse = self 330 | .client 331 | .post(&self.url) 332 | .body("{\"method\":\"eth_gasPrice\",\"params\":[],\"id\":1,\"jsonrpc\":\"2.0\"}") 333 | .headers(self.headers.clone()) 334 | .send()? 335 | .json()?; 336 | 337 | match json.error { 338 | Some(err) => Err(err.message.into()), 339 | None => Ok(u128::from_str_radix( 340 | json.result.unwrap().strip_prefix("0x").unwrap(), 341 | 16, 342 | )?), 343 | } 344 | } 345 | 346 | ///The `get_code()` function takes an address, block param or block number, and attempts to return a deserialized balance as `Ok(u128)`. Returns an `Err()` on JSON-RPC errors. 347 | ///## Example 348 | ///```rust 349 | ///use ethrs::provider::Provider; 350 | ///use ethrs::types::U256; 351 | ///use std::error::Error; 352 | /// 353 | ///fn main() -> Result<(), Box> { 354 | /// let provider = Provider::new("https://rpc.sepolia.org"); 355 | /// assert!(provider 356 | /// .get_balance("0x0000000000000000000000000000000000000000", None, None)? // fetches the latest balance of this address 357 | /// > 0); 358 | /// Ok(()) 359 | ///} 360 | ///``` 361 | pub fn get_balance( 362 | &self, 363 | address: &str, 364 | block_param: Option, 365 | block_number: Option, 366 | ) -> Result> { 367 | match ADDRESS_REGEX.is_match(address) { 368 | true => { 369 | let mut payload = String::new(); 370 | payload.push_str("{\"method\":\"eth_getBalance\",\"params\":[\""); 371 | payload.push_str(address); 372 | payload.push_str("\",\""); 373 | match block_param { 374 | Some(DefaultBlockParam::EARLIEST) => payload.push_str("earliest"), 375 | Some(DefaultBlockParam::FINALIZED) => payload.push_str("finalized"), 376 | Some(DefaultBlockParam::SAFE) => payload.push_str("safe"), 377 | Some(DefaultBlockParam::LATEST) => payload.push_str("latest"), 378 | Some(DefaultBlockParam::PENDING) => payload.push_str("pending"), 379 | None => match block_number { 380 | Some(block) => payload.push_str(&format!("0x{block:x}")), 381 | None => payload.push_str("latest"), 382 | }, 383 | } 384 | 385 | payload.push_str("\"],\"id\":1,\"jsonrpc\":\"2.0\"}"); 386 | 387 | let json: RPCResponse = self 388 | .client 389 | .post(&self.url) 390 | .body(payload.clone()) 391 | .headers(self.headers.clone()) 392 | .send()? 393 | .json()?; 394 | 395 | match json.error { 396 | Some(err) => Err(err.message.into()), 397 | None => Ok(u128::from_str_radix( 398 | json.result.unwrap().strip_prefix("0x").unwrap(), 399 | 16, 400 | )?), 401 | } 402 | } 403 | false => Err("Invalid address".into()), 404 | } 405 | } 406 | 407 | ///The `get_storage_at()` function takes an address, slot, block param or block number, and attempts to return a deserialized code hexstring as `Ok(String)`. Returns an `Err()` on JSON-RPC errors. 408 | ///## Example 409 | ///```rust 410 | ///use ethrs::provider::Provider; 411 | ///use ethrs::types::U256; 412 | ///use std::error::Error; 413 | /// 414 | ///fn main() -> Result<(), Box> { 415 | /// let provider = Provider::new("https://rpc.sepolia.org"); 416 | /// assert!(provider 417 | /// .get_storage_at("0x6f14c02fc1f78322cfd7d707ab90f18bad3b54f5", "0x0", None, None)? // fetches the latest code at this address 418 | /// != "0x0"); 419 | /// Ok(()) 420 | ///} 421 | ///``` 422 | pub fn get_storage_at( 423 | &self, 424 | address: &str, 425 | slot: &str, 426 | block_param: Option, 427 | block_number: Option, 428 | ) -> Result> { 429 | match ADDRESS_REGEX.is_match(address) { 430 | true => match SLOT_REGEX.is_match(slot) { 431 | true => { 432 | let mut payload = String::new(); 433 | payload.push_str("{\"method\":\"eth_getStorageAt\",\"params\":[\""); 434 | payload.push_str(address); 435 | payload.push_str("\",\""); 436 | payload.push_str(slot); 437 | payload.push_str("\",\""); 438 | match block_param { 439 | Some(DefaultBlockParam::EARLIEST) => payload.push_str("earliest"), 440 | Some(DefaultBlockParam::FINALIZED) => payload.push_str("finalized"), 441 | Some(DefaultBlockParam::SAFE) => payload.push_str("safe"), 442 | Some(DefaultBlockParam::LATEST) => payload.push_str("latest"), 443 | Some(DefaultBlockParam::PENDING) => payload.push_str("pending"), 444 | None => match block_number { 445 | Some(block) => payload.push_str(&format!("0x{block:x}")), 446 | None => payload.push_str("latest"), 447 | }, 448 | } 449 | payload.push_str("\"],\"id\":1,\"jsonrpc\":\"2.0\"}"); 450 | 451 | let json: RPCResponse = self 452 | .client 453 | .post(&self.url) 454 | .body(payload.clone()) 455 | .headers(self.headers.clone()) 456 | .send()? 457 | .json()?; 458 | 459 | match json.error { 460 | Some(err) => Err(err.message.into()), 461 | None => Ok(json.result.unwrap()), 462 | } 463 | } 464 | false => Err("Invalid slot".into()), 465 | }, 466 | false => Err("Invalid address".into()), 467 | } 468 | } 469 | 470 | ///The `get_code()` function takes an address, block param or block number, and attempts to return a deserialized string as `Ok(String)`. Returns an `Err()` on JSON-RPC errors. 471 | ///## Example 472 | ///```rust 473 | ///use ethrs::provider::Provider; 474 | ///use ethrs::types::U256; 475 | ///use std::error::Error; 476 | /// 477 | ///fn main() -> Result<(), Box> { 478 | /// let provider = Provider::new("https://rpc.sepolia.org"); 479 | /// assert!(provider 480 | /// .get_code("0x6f14c02fc1f78322cfd7d707ab90f18bad3b54f5", None, None)? // fetches the latest code at this address 481 | /// != "0x0"); 482 | /// Ok(()) 483 | ///} 484 | ///``` 485 | pub fn get_code( 486 | &self, 487 | address: &str, 488 | block_param: Option, 489 | block_number: Option, 490 | ) -> Result> { 491 | match ADDRESS_REGEX.is_match(address) { 492 | true => { 493 | let mut payload = String::new(); 494 | payload.push_str("{\"method\":\"eth_getCode\",\"params\":[\""); 495 | payload.push_str(address); 496 | payload.push_str("\",\""); 497 | match block_param { 498 | Some(DefaultBlockParam::EARLIEST) => payload.push_str("earliest"), 499 | Some(DefaultBlockParam::FINALIZED) => payload.push_str("finalized"), 500 | Some(DefaultBlockParam::SAFE) => payload.push_str("safe"), 501 | Some(DefaultBlockParam::LATEST) => payload.push_str("latest"), 502 | Some(DefaultBlockParam::PENDING) => payload.push_str("pending"), 503 | None => match block_number { 504 | Some(block) => payload.push_str(&format!("0x{block:x}")), 505 | None => payload.push_str("latest"), 506 | }, 507 | } 508 | 509 | payload.push_str("\"],\"id\":1,\"jsonrpc\":\"2.0\"}"); 510 | 511 | let json: RPCResponse = self 512 | .client 513 | .post(&self.url) 514 | .body(payload.clone()) 515 | .headers(self.headers.clone()) 516 | .send()? 517 | .json()?; 518 | 519 | match json.error { 520 | Some(err) => Err(err.message.into()), 521 | None => Ok(json.result.unwrap()), 522 | } 523 | } 524 | false => Err("Invalid address".into()), 525 | } 526 | } 527 | 528 | ///The `get_transaction_count()` function takes an address, block param or block number, and attempts to return a deserialized integer as `Ok(u128)`. Returns an `Err()` on JSON-RPC errors. 529 | ///## Example 530 | ///```rust 531 | ///use ethrs::provider::Provider; 532 | ///use std::error::Error; 533 | /// 534 | ///fn main() -> Result<(), Box> { 535 | /// let provider = Provider::new("https://rpc.sepolia.org"); 536 | /// assert!(provider 537 | /// .get_transaction_count("0xec65818ff0f8b071e587a0bbdbecc94de739b6ec", None, None)? // fetches the latest transaction count for this address 538 | /// > 0); 539 | /// Ok(()) 540 | ///} 541 | ///``` 542 | pub fn get_transaction_count( 543 | &self, 544 | address: &str, 545 | block_param: Option, 546 | block_number: Option, 547 | ) -> Result> { 548 | match ADDRESS_REGEX.is_match(address) { 549 | true => { 550 | let mut payload = String::new(); 551 | payload.push_str("{\"method\":\"eth_getTransactionCount\",\"params\":[\""); 552 | payload.push_str(address); 553 | payload.push_str("\",\""); 554 | match block_param { 555 | Some(DefaultBlockParam::EARLIEST) => payload.push_str("earliest"), 556 | Some(DefaultBlockParam::FINALIZED) => payload.push_str("finalized"), 557 | Some(DefaultBlockParam::SAFE) => payload.push_str("safe"), 558 | Some(DefaultBlockParam::LATEST) => payload.push_str("latest"), 559 | Some(DefaultBlockParam::PENDING) => payload.push_str("pending"), 560 | None => match block_number { 561 | Some(block) => payload.push_str(&format!("0x{block:x}")), 562 | None => payload.push_str("latest"), 563 | }, 564 | } 565 | 566 | payload.push_str("\"],\"id\":1,\"jsonrpc\":\"2.0\"}"); 567 | 568 | let json: RPCResponse = self 569 | .client 570 | .post(&self.url) 571 | .body(payload.clone()) 572 | .headers(self.headers.clone()) 573 | .send()? 574 | .json()?; 575 | 576 | match json.error { 577 | Some(err) => Err(err.message.into()), 578 | None => Ok(u128::from_str_radix( 579 | json.result.unwrap().strip_prefix("0x").unwrap(), 580 | 16, 581 | )?), 582 | } 583 | } 584 | false => Err("Invalid address".into()), 585 | } 586 | } 587 | 588 | ///The `get_block_transaction_count_by_hash()` function takes a blockhash and attempts to return a deserialized integer as `Ok(Some(u128))`. Returns a `None` when blockhash is not mined and returns an `Err()` on JSON-RPC errors. 589 | ///## Example 590 | ///```rust 591 | ///use ethrs::provider::Provider; 592 | ///use std::error::Error; 593 | /// 594 | ///fn main() -> Result<(), Box> { 595 | /// let provider = Provider::new("https://rpc.sepolia.org"); 596 | /// assert!(provider 597 | /// .get_block_transaction_count_by_hash("0x6c4925c897c45d377d8fb3ef59df7e0cf97604fc85b909bb806818368fdc6b07")? // fetches the latest transaction count for this address 598 | /// == Some(5)); 599 | /// Ok(()) 600 | ///} 601 | ///``` 602 | pub fn get_block_transaction_count_by_hash( 603 | &self, 604 | block_hash: &str, 605 | ) -> Result, Box> { 606 | match BLOCKHASH_REGEX.is_match(block_hash) { 607 | true => { 608 | let mut payload = String::new(); 609 | payload 610 | .push_str("{\"method\":\"eth_getBlockTransactionCountByHash\",\"params\":[\""); 611 | payload.push_str(block_hash); 612 | payload.push_str("\"],\"id\":1,\"jsonrpc\":\"2.0\"}"); 613 | 614 | let json: RPCResponse = self 615 | .client 616 | .post(&self.url) 617 | .body(payload.clone()) 618 | .headers(self.headers.clone()) 619 | .send()? 620 | .json()?; 621 | 622 | match json.error { 623 | Some(err) => Err(err.message.into()), 624 | None => match json.result { 625 | Some(result) => Ok(Some(u128::from_str_radix( 626 | result.strip_prefix("0x").unwrap(), 627 | 16, 628 | )?)), 629 | None => Ok(None), 630 | }, 631 | } 632 | } 633 | false => Err("Invalid block hash".into()), 634 | } 635 | } 636 | 637 | ///The `get_block_by_hash()` function takes a block hash and attempts to return a deserialized block *without transactions* as `Ok(Some(Block))`. If no such block exists, returns `Ok(None)` and returns an `Err()` on JSON-RPC errors. Pending blocks will have some fields serialized as `None` types. 638 | ///## Example 639 | ///```rust 640 | ///use ethrs::provider::Provider; 641 | ///use ethrs::types::U256; 642 | ///use std::error::Error; 643 | /// 644 | ///fn main() -> Result<(), Box> { 645 | /// let provider = Provider::new("https://rpc.sepolia.org"); 646 | /// assert!(provider 647 | /// .get_block_by_hash("0x7caebcb62b8fdd21673bcd7d3737f3e6dc18915e08ef3c868cb42aa78eb95d06")? // fetches the block by hash 648 | /// .is_some()); 649 | /// Ok(()) 650 | ///} 651 | ///``` 652 | pub fn get_block_by_hash(&self, block_hash: &str) -> Result, Box> { 653 | match BLOCKHASH_REGEX.is_match(block_hash) { 654 | true => { 655 | let mut payload = String::new(); 656 | match write!(payload, "{{\"method\":\"eth_getBlockByHash\",\"params\":[\"{block_hash}\",false],\"id\":1,\"jsonrpc\":\"2.0\"}}") { 657 | Ok(_) => (), 658 | Err(err) => return Err(err.into()), 659 | }; 660 | 661 | let json: BlockRPCResponse = self 662 | .client 663 | .post(&self.url) 664 | .body(payload.clone()) 665 | .headers(self.headers.clone()) 666 | .send()? 667 | .json()?; 668 | 669 | match json.error { 670 | Some(err) => Err(err.into()), 671 | None => Ok(json.result), 672 | } 673 | } 674 | false => Err("Invalid block hash".into()), 675 | } 676 | } 677 | 678 | ///The `get_block_by_hash_with_tx()` function takes a block hash and attempts to return a deserialized block *with transactions* as `Ok(Some(BlockWithTx))`. If no such block exists, returns `Ok(None)` and returns an `Err()` on JSON-RPC errors. Pending blocks will have some fields serialized as `None` types. 679 | ///## Example 680 | ///```rust 681 | ///use ethrs::provider::Provider; 682 | ///use ethrs::types::U256; 683 | ///use std::error::Error; 684 | /// 685 | ///fn main() -> Result<(), Box> { 686 | /// let provider = Provider::new("https://rpc.sepolia.org"); 687 | /// assert!(provider 688 | /// .get_block_by_hash_with_tx("0x7caebcb62b8fdd21673bcd7d3737f3e6dc18915e08ef3c868cb42aa78eb95d06")? // fetches the block by hash with txs 689 | /// .is_some()); 690 | /// Ok(()) 691 | ///} 692 | ///``` 693 | pub fn get_block_by_hash_with_tx( 694 | &self, 695 | block_hash: &str, 696 | ) -> Result, Box> { 697 | match BLOCKHASH_REGEX.is_match(block_hash) { 698 | true => { 699 | let mut payload = String::new(); 700 | match write!(payload, "{{\"method\":\"eth_getBlockByHash\",\"params\":[\"{block_hash}\",true],\"id\":1,\"jsonrpc\":\"2.0\"}}") { 701 | Ok(_) => (), 702 | Err(err) => return Err(err.into()), 703 | }; 704 | let json: BlockWithTxRPCResponse = self 705 | .client 706 | .post(&self.url) 707 | .body(payload.clone()) 708 | .headers(self.headers.clone()) 709 | .send()? 710 | .json()?; 711 | 712 | match json.error { 713 | Some(err) => Err(err.into()), 714 | None => Ok(json.result), 715 | } 716 | } 717 | false => Err("Invalid block hash".into()), 718 | } 719 | } 720 | 721 | ///The `get_block_by_number()` function takes a default block param or block number and attempts to return a deserialized block *without transactions* as `Ok(Some(Block))`. If no such block exists, returns `Ok(None)` and returns an `Err()` on JSON-RPC errors. Pending blocks will have some fields serialized as `None` types. 722 | ///## Example 723 | ///```rust 724 | ///use ethrs::provider::Provider; 725 | ///use ethrs::types::U256; 726 | ///use std::error::Error; 727 | /// 728 | ///fn main() -> Result<(), Box> { 729 | /// let provider = Provider::new("https://rpc.sepolia.org"); 730 | /// assert!(provider 731 | /// .get_block_by_number(None, None)? // fetches the latest block 732 | /// .is_some()); 733 | /// Ok(()) 734 | ///} 735 | ///``` 736 | pub fn get_block_by_number( 737 | &self, 738 | block_param: Option, 739 | block_number: Option, 740 | ) -> Result, Box> { 741 | let mut payload = String::new(); 742 | payload.push_str("{\"method\":\"eth_getBlockByNumber\",\"params\":[\""); 743 | match block_param { 744 | Some(DefaultBlockParam::EARLIEST) => payload.push_str("earliest"), 745 | Some(DefaultBlockParam::FINALIZED) => payload.push_str("finalized"), 746 | Some(DefaultBlockParam::SAFE) => payload.push_str("safe"), 747 | Some(DefaultBlockParam::LATEST) => payload.push_str("latest"), 748 | Some(DefaultBlockParam::PENDING) => payload.push_str("pending"), 749 | None => match block_number { 750 | Some(block) => payload.push_str(&format!("0x{block:x}")), 751 | None => payload.push_str("latest"), 752 | }, 753 | } 754 | 755 | payload.push_str("\",false],\"id\":1,\"jsonrpc\":\"2.0\"}"); 756 | 757 | let json: BlockRPCResponse = self 758 | .client 759 | .post(&self.url) 760 | .body(payload.clone()) 761 | .headers(self.headers.clone()) 762 | .send()? 763 | .json()?; 764 | 765 | match json.error { 766 | Some(err) => Err(err.into()), 767 | None => Ok(json.result), 768 | } 769 | } 770 | 771 | ///The `get_block_by_number_with_tx()` function takes a default block param or block number and attempts to return a deserialized block *with transactions* as `Ok(Some(BlockWithTx))`. If no such block exists, returns `Ok(None)` and returns an `Err()` on JSON-RPC errors. Pending blocks will have some fields serialized as `None` types. 772 | ///## Example 773 | ///```rust 774 | ///use ethrs::provider::Provider; 775 | ///use ethrs::types::U256; 776 | ///use std::error::Error; 777 | /// 778 | ///fn main() -> Result<(), Box> { 779 | /// let provider = Provider::new("https://rpc.sepolia.org"); 780 | /// assert!(provider 781 | /// .get_block_by_number_with_tx(None, None)? // fetches the latest block 782 | /// .is_some()); 783 | /// Ok(()) 784 | ///} 785 | ///``` 786 | pub fn get_block_by_number_with_tx( 787 | &self, 788 | block_param: Option, 789 | block_number: Option, 790 | ) -> Result, Box> { 791 | let mut payload = String::new(); 792 | payload.push_str("{\"method\":\"eth_getBlockByNumber\",\"params\":[\""); 793 | match block_param { 794 | Some(DefaultBlockParam::EARLIEST) => payload.push_str("earliest"), 795 | Some(DefaultBlockParam::FINALIZED) => payload.push_str("finalized"), 796 | Some(DefaultBlockParam::SAFE) => payload.push_str("safe"), 797 | Some(DefaultBlockParam::LATEST) => payload.push_str("latest"), 798 | Some(DefaultBlockParam::PENDING) => payload.push_str("pending"), 799 | None => match block_number { 800 | Some(block) => payload.push_str(&format!("0x{block:x}")), 801 | None => payload.push_str("latest"), 802 | }, 803 | } 804 | 805 | payload.push_str("\",true],\"id\":1,\"jsonrpc\":\"2.0\"}"); 806 | 807 | let json: BlockWithTxRPCResponse = self 808 | .client 809 | .post(&self.url) 810 | .body(payload.clone()) 811 | .headers(self.headers.clone()) 812 | .send()? 813 | .json()?; 814 | 815 | match json.error { 816 | Some(err) => Err(err.into()), 817 | None => Ok(json.result), 818 | } 819 | } 820 | 821 | ///The `get_transaction_by_hash()` function takes a transaction hash attempts to return a deserialized transaction as `Ok(Some(Transaction))`. If no such transaction exists, returns `Ok(None)` and returns an `Err()` on JSON-RPC errors. Pending transactions will have some fields serialized as `None` types. 822 | ///## Example 823 | ///```rust 824 | ///use ethrs::provider::Provider; 825 | ///use ethrs::types::U256; 826 | ///use std::error::Error; 827 | /// 828 | ///fn main() -> Result<(), Box> { 829 | /// let provider = Provider::new("https://rpc.sepolia.org"); 830 | /// assert!(provider 831 | /// .get_transaction_by_hash("0xfb09cfce0695a6843ee3ad5ed4505ca4c8fc0b32f33c1ee12548ba78f0ee52be")? 832 | /// .is_some()); 833 | /// Ok(()) 834 | ///} 835 | ///``` 836 | pub fn get_transaction_by_hash( 837 | &self, 838 | txhash: &str, 839 | ) -> Result, Box> { 840 | match BLOCKHASH_REGEX.is_match(txhash) { 841 | true => { 842 | let mut payload = String::new(); 843 | match write!(payload, "{{\"method\":\"eth_getTransactionByHash\",\"params\":[\"{txhash}\"],\"id\":1,\"jsonrpc\":\"2.0\"}}") { 844 | Ok(_) => (), 845 | Err(err) => return Err(err.into()) 846 | } 847 | 848 | let json: TxRPCResponse = self 849 | .client 850 | .post(&self.url) 851 | .body(payload.clone()) 852 | .headers(self.headers.clone()) 853 | .send()? 854 | .json()?; 855 | 856 | match json.error { 857 | Some(err) => Err(err.into()), 858 | None => Ok(json.result), 859 | } 860 | } 861 | false => Err("Invalid txhash".into()), 862 | } 863 | } 864 | 865 | ///The `get_transaction_by_block_hash_and_index()` function takes a block hash and transaction index and attempts to return a deserialized transaction as `Ok(Some(Transaction))`. If no such transaction exists on the index, returns `Ok(None)` and returns an `Err()` on JSON-RPC errors. 866 | ///## Example 867 | ///```rust 868 | ///use ethrs::provider::Provider; 869 | ///use ethrs::types::U256; 870 | ///use std::error::Error; 871 | /// 872 | ///fn main() -> Result<(), Box> { 873 | /// let provider = Provider::new("https://rpc.sepolia.org"); 874 | /// assert!(provider 875 | /// .get_transaction_by_block_hash_and_index("0xc49f9290e07575fbcf91a9349721edaff45a6600add9281e48a2948f01c1d8d4", U256::from(1))? // fetches the block by hash and returns the tx at index 1 876 | /// .is_some()); 877 | /// Ok(()) 878 | ///} 879 | ///``` 880 | pub fn get_transaction_by_block_hash_and_index( 881 | &self, 882 | block_hash: &str, 883 | idx: U256, 884 | ) -> Result, Box> { 885 | match BLOCKHASH_REGEX.is_match(block_hash) { 886 | true => { 887 | let mut payload = String::new(); 888 | match write!(payload, "{{\"method\":\"eth_getTransactionByBlockHashAndIndex\",\"params\":[\"{block_hash}\",\"0x{idx:x}\"],\"id\":1,\"jsonrpc\":\"2.0\"}}") { 889 | Ok(_) => (), 890 | Err(err) => return Err(err.into()) 891 | } 892 | 893 | let json: TxRPCResponse = self 894 | .client 895 | .post(&self.url) 896 | .body(payload.clone()) 897 | .headers(self.headers.clone()) 898 | .send()? 899 | .json()?; 900 | 901 | match json.error { 902 | Some(err) => Err(err.into()), 903 | None => Ok(json.result), 904 | } 905 | } 906 | false => Err("Invalid blockhash".into()), 907 | } 908 | } 909 | 910 | ///The `get_transaction_by_block_number_and_index()` function takes a block number and transaction index and attempts to return a deserialized transaction as `Ok(Some(Transaction))`. If no such transaction exists on the index, returns `Ok(None)` and returns an `Err()` on JSON-RPC errors. 911 | ///## Example 912 | ///```rust 913 | ///use ethrs::provider::Provider; 914 | ///use ethrs::types::U256; 915 | ///use std::error::Error; 916 | /// 917 | ///fn main() -> Result<(), Box> { 918 | /// let provider = Provider::new("https://rpc.sepolia.org"); 919 | /// assert!(provider 920 | /// .get_transaction_by_block_number_and_index(U256::from(2893800), U256::from(1))? // fetches the block by number and returns the tx at index 1 921 | /// .is_some()); 922 | /// Ok(()) 923 | ///} 924 | ///``` 925 | pub fn get_transaction_by_block_number_and_index( 926 | &self, 927 | block_number: U256, 928 | idx: U256, 929 | ) -> Result, Box> { 930 | let mut payload = String::new(); 931 | match write!(payload, "{{\"method\":\"eth_getTransactionByBlockNumberAndIndex\",\"params\":[\"0x{block_number:x}\",\"0x{idx:x}\"],\"id\":1,\"jsonrpc\":\"2.0\"}}") { 932 | Ok(_) => (), 933 | Err(err) => return Err(err.into()) 934 | } 935 | 936 | let json: TxRPCResponse = self 937 | .client 938 | .post(&self.url) 939 | .body(payload.clone()) 940 | .headers(self.headers.clone()) 941 | .send()? 942 | .json()?; 943 | 944 | match json.error { 945 | Some(err) => Err(err.into()), 946 | None => Ok(json.result), 947 | } 948 | } 949 | 950 | ///The `get_transaction_receipt()` function takes transaction hash and attempts to return a deserialized transaction receipt as `Ok(Some(TransactionReceipt))`. If no such transaction exists, returns `Ok(None)` and returns an `Err()` on JSON-RPC errors. 951 | ///## Example 952 | ///```rust 953 | ///use ethrs::provider::Provider; 954 | ///use ethrs::types::U256; 955 | ///use std::error::Error; 956 | /// 957 | ///fn main() -> Result<(), Box> { 958 | /// let provider = Provider::new("https://rpc.sepolia.org"); 959 | /// assert!(provider 960 | /// .get_transaction_receipt("0x71d6059608006e73a233978ee092e7a2066b2556bc4a31dfe9be1f23328ce36a")?.is_some()); 961 | /// Ok(()) 962 | ///} 963 | ///``` 964 | pub fn get_transaction_receipt( 965 | &self, 966 | txhash: &str, 967 | ) -> Result, Box> { 968 | match BLOCKHASH_REGEX.is_match(txhash) { 969 | true => { 970 | let mut payload = String::new(); 971 | match write!(payload, "{{\"method\":\"eth_getTransactionReceipt\",\"params\":[\"{txhash}\"],\"id\":1,\"jsonrpc\":\"2.0\"}}") { 972 | Ok(_) => (), 973 | Err(err) => return Err(err.into()) 974 | } 975 | 976 | let json: TxReceiptRPCResponse = self 977 | .client 978 | .post(&self.url) 979 | .body(payload.clone()) 980 | .headers(self.headers.clone()) 981 | .send()? 982 | .json()?; 983 | 984 | match json.error { 985 | Some(err) => Err(err.into()), 986 | None => Ok(json.result), 987 | } 988 | } 989 | false => Err("Invalid txhash".into()), 990 | } 991 | } 992 | 993 | ///The `send_transaction()` function takes a transaction input struct, sends it and attempts to return a deserialized transaction hash as `Ok(String)`. If no such transaction exists, returns `Ok(0x0...)` and returns an `Err()` on JSON-RPC errors. 994 | ///## Example 995 | ///```rust 996 | ///use ethrs::provider::{Provider, TransactionInput}; 997 | ///use ethrs::types::U256; 998 | ///use std::error::Error; 999 | /// 1000 | ///fn main() -> Result<(), Box> { 1001 | /// let provider = Provider::new("https://rpc.sepolia.org"); 1002 | /// let tx = TransactionInput { 1003 | /// from: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_owned(), 1004 | /// to: Some("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266".to_owned()), 1005 | /// gas: Some(U256::from(21000)), 1006 | /// gas_price: Some(U256::from(1)), 1007 | /// value: Some(U256::from(1)), 1008 | /// data: Some("0xFF".to_owned()), 1009 | /// nonce: Some(U256::from(0)), 1010 | /// }; 1011 | /// // the RPC call itself will fail because the account is not unlocked 1012 | /// assert!(provider.send_transaction(tx).is_err()); 1013 | /// Ok(()) 1014 | ///} 1015 | ///``` 1016 | pub fn send_transaction(&self, tx: TransactionInput) -> Result> { 1017 | let mut payload = String::new(); 1018 | 1019 | let tx_json = serde_json::to_string(&tx)?; 1020 | 1021 | match write!(payload, "{{\"method\":\"eth_sendTransaction\",\"params\":[{tx_json}],\"id\":1,\"jsonrpc\":\"2.0\"}}") { 1022 | Ok(_) => (), 1023 | Err(err) => return Err(err.into()) 1024 | } 1025 | 1026 | let json: RPCResponse = self 1027 | .client 1028 | .post(&self.url) 1029 | .body(payload.clone()) 1030 | .headers(self.headers.clone()) 1031 | .send()? 1032 | .json()?; 1033 | 1034 | match json.error { 1035 | Some(err) => Err(err.message.into()), 1036 | None => match json.result { 1037 | Some(hash) => Ok(hash), 1038 | None => Err("No txhash returned".into()), 1039 | }, 1040 | } 1041 | } 1042 | 1043 | ///The `call()` function takes a call input struct, sends it and attempts to return deserialized return data as `Ok(String)`. If no data is returned or a transaction is sent to an EOA, returns `Ok(0x0...)` and returns an `Err()` on JSON-RPC errors. 1044 | ///## Example 1045 | ///```rust 1046 | ///use ethrs::provider::{Provider, CallInput}; 1047 | ///use ethrs::types::U256; 1048 | ///use std::error::Error; 1049 | /// 1050 | ///fn main() -> Result<(), Box> { 1051 | /// let provider = Provider::new("https://rpc.sepolia.org"); 1052 | /// let tx = CallInput { 1053 | /// from: None, 1054 | /// to: "0xfd6470334498a1f26db0c5915b026670499b2632".to_owned(), 1055 | /// gas: None, 1056 | /// gas_price: None, 1057 | /// value: None, 1058 | /// data: Some("0xd800df5c".to_owned()), 1059 | /// }; 1060 | /// assert_eq!(provider.call(tx, None, None)?, "0x00000000000000000000000000000000000000000000000000000000000003e8".to_owned()); 1061 | /// Ok(()) 1062 | ///} 1063 | ///``` 1064 | pub fn call( 1065 | &self, 1066 | tx: CallInput, 1067 | block_param: Option, 1068 | block_number: Option, 1069 | ) -> Result> { 1070 | let mut payload = String::new(); 1071 | 1072 | let tx_json = serde_json::to_string(&tx)?; 1073 | 1074 | payload.push_str("{\"method\":\"eth_call\",\"params\":["); 1075 | payload.push_str(&tx_json); 1076 | payload.push_str(",\""); 1077 | match block_param { 1078 | Some(DefaultBlockParam::EARLIEST) => payload.push_str("earliest"), 1079 | Some(DefaultBlockParam::FINALIZED) => payload.push_str("finalized"), 1080 | Some(DefaultBlockParam::SAFE) => payload.push_str("safe"), 1081 | Some(DefaultBlockParam::LATEST) => payload.push_str("latest"), 1082 | Some(DefaultBlockParam::PENDING) => payload.push_str("pending"), 1083 | None => match block_number { 1084 | Some(block) => payload.push_str(&format!("0x{block:x}")), 1085 | None => payload.push_str("latest"), 1086 | }, 1087 | } 1088 | payload.push_str("\"],\"id\":1,\"jsonrpc\":\"2.0\"}"); 1089 | 1090 | let json: RPCResponse = self 1091 | .client 1092 | .post(&self.url) 1093 | .body(payload.clone()) 1094 | .headers(self.headers.clone()) 1095 | .send()? 1096 | .json()?; 1097 | 1098 | match json.error { 1099 | Some(err) => Err(err.message.into()), 1100 | None => match json.result { 1101 | Some(data) => Ok(data), 1102 | None => Err("No data returned".into()), 1103 | }, 1104 | } 1105 | } 1106 | } 1107 | --------------------------------------------------------------------------------