├── .github └── workflows │ ├── add-to-devtools.yml │ ├── release-plz.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── api ├── Cargo.toml ├── examples │ ├── account_key_pooling.rs │ ├── contract_source_metadata.rs │ ├── create_account_and_send_near.rs │ ├── deploy_and_call_method.rs │ ├── ft.rs │ ├── global_deploy.rs │ ├── nep_413_signing_message.rs │ ├── nft.rs │ ├── run_all.sh │ ├── sign_options.rs │ ├── specify_backup_rpc.rs │ └── specifying_block.rs ├── resources │ ├── counter.wasm │ ├── fungible_token.wasm │ └── nft.wasm ├── src │ ├── account │ │ ├── create.rs │ │ └── mod.rs │ ├── chain.rs │ ├── common │ │ ├── mod.rs │ │ ├── query │ │ │ ├── block_rpc.rs │ │ │ ├── handlers │ │ │ │ ├── mod.rs │ │ │ │ └── transformers.rs │ │ │ ├── mod.rs │ │ │ ├── query_request.rs │ │ │ ├── query_rpc.rs │ │ │ └── validator_rpc.rs │ │ ├── send.rs │ │ └── utils.rs │ ├── config.rs │ ├── contract.rs │ ├── errors.rs │ ├── fastnear.rs │ ├── lib.rs │ ├── signer │ │ ├── keystore.rs │ │ ├── ledger.rs │ │ ├── mod.rs │ │ └── secret_key.rs │ ├── stake.rs │ ├── storage.rs │ ├── tokens.rs │ └── transactions.rs └── tests │ ├── account.rs │ ├── contract.rs │ ├── global_contracts.rs │ └── multiple_tx_at_same_time_from_same-_user.rs ├── cspell.json ├── rust-toolchain.toml └── types ├── Cargo.toml └── src ├── account.rs ├── contract.rs ├── crypto ├── mod.rs ├── public_key.rs ├── secret_key.rs └── signature.rs ├── errors.rs ├── ft.rs ├── json ├── integers.rs ├── mod.rs └── vector.rs ├── lib.rs ├── nft.rs ├── reference.rs ├── signable_message.rs ├── stake.rs ├── storage.rs ├── tokens.rs ├── transaction ├── actions.rs ├── delegate_action.rs ├── mod.rs └── result.rs └── utils └── mod.rs /.github/workflows/add-to-devtools.yml: -------------------------------------------------------------------------------- 1 | name: 'Add to DevTools Project' 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - reopened 8 | pull_request_target: 9 | types: 10 | - opened 11 | - reopened 12 | 13 | jobs: 14 | add-to-project: 15 | name: Add issue/PR to project 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/add-to-project@v1.0.0 19 | with: 20 | # add to DevTools Project #156 21 | project-url: https://github.com/orgs/near/projects/156 22 | github-token: ${{ secrets.PROJECT_GH_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/release-plz.yml: -------------------------------------------------------------------------------- 1 | name: Release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | release-plz: 14 | name: Release-plz 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | token: ${{ secrets.RELEASE_PLZ_GITHUB_TOKEN }} 22 | - name: Install Rust toolchain 23 | uses: dtolnay/rust-toolchain@stable 24 | - name: Install libudev (Linux only) 25 | run: sudo apt update && sudo apt-get -y install libudev-dev libsystemd-dev 26 | - name: Run release-plz 27 | uses: MarcoIeni/release-plz-action@v0.5 28 | env: 29 | # https://marcoieni.github.io/release-plz/github-action.html#triggering-further-workflow-runs 30 | GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_GITHUB_TOKEN }} 31 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test & Release 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | RUSTFLAGS: '-D warnings' 11 | CARGO_INCREMENTAL: 0 12 | RUST_BACKTRACE: short 13 | 14 | jobs: 15 | clippy: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Install libudev (Linux only) 21 | if: runner.os == 'Linux' 22 | run: sudo apt-get update && sudo apt-get -y install libudev-dev libsystemd-dev 23 | - uses: Swatinem/rust-cache@v2 24 | - name: Run clippy 25 | run: cargo clippy --all-targets -- -D clippy::all -D clippy::nursery 26 | 27 | cargo-fmt: 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Run cargo fmt 33 | run: cargo fmt --check 34 | 35 | cargo-doc: 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | - name: Install libudev (Linux only) 41 | if: runner.os == 'Linux' 42 | run: sudo apt-get update && sudo apt-get -y install libudev-dev libsystemd-dev 43 | - name: run cargo doc 44 | run: RUSTDOCFLAGS="-D warnings" cargo doc --all-features --document-private-items 45 | 46 | check-windows: 47 | needs: cargo-fmt 48 | runs-on: windows-latest 49 | 50 | steps: 51 | - uses: actions/checkout@v4 52 | - uses: Swatinem/rust-cache@v2 53 | - name: Run cargo check 54 | run: cargo check --release 55 | 56 | no_features_check: 57 | needs: cargo-fmt 58 | runs-on: ubuntu-latest 59 | steps: 60 | - uses: actions/checkout@v4 61 | - uses: Swatinem/rust-cache@v2 62 | - name: Run cargo check 63 | run: cargo check --no-default-features 64 | 65 | examples: 66 | needs: cargo-fmt 67 | runs-on: ubuntu-latest 68 | steps: 69 | - uses: actions/checkout@v4 70 | - uses: Swatinem/rust-cache@v2 71 | - name: Install libudev (Linux only) 72 | if: runner.os == 'Linux' 73 | run: sudo apt-get update && sudo apt-get -y install libudev-dev libsystemd-dev 74 | - name: Run examples 75 | run: | 76 | cd api/examples 77 | ./run_all.sh 78 | 79 | get_msrv: 80 | runs-on: ubuntu-latest 81 | outputs: 82 | version: ${{ steps.rust_msrv.outputs.version }} 83 | steps: 84 | - uses: actions/checkout@v4 85 | - name: Get MSRV 86 | id: rust_msrv 87 | run: | 88 | RUST_MSRV="$(cat Cargo.toml | sed -n 's/rust-version *= *"\(.*\)"/\1/p')" 89 | echo "Found MSRV: $RUST_MSRV" 90 | echo "version=$RUST_MSRV" >> "$GITHUB_OUTPUT" 91 | 92 | test: 93 | needs: [cargo-fmt, get_msrv] 94 | strategy: 95 | fail-fast: false 96 | matrix: 97 | platform: [ubuntu-latest, macos-latest] 98 | toolchain: 99 | - stable 100 | - ${{ needs.get_msrv.outputs.version }} 101 | runs-on: ${{ matrix.platform }} 102 | name: CI with ${{ matrix.toolchain }} 103 | steps: 104 | - uses: actions/checkout@v4 105 | - name: "${{ matrix.toolchain }}" 106 | uses: actions-rs/toolchain@v1 107 | with: 108 | profile: minimal 109 | toolchain: ${{ matrix.toolchain }} 110 | default: true 111 | - uses: Swatinem/rust-cache@v2 112 | - name: Install libudev (Linux only) 113 | if: runner.os == 'Linux' 114 | run: sudo apt-get update && sudo apt-get -y install libudev-dev libsystemd-dev 115 | - name: Check with stable features 116 | run: cargo check --examples --all-features 117 | - name: Run tests 118 | run: cargo test 119 | spellcheck: 120 | runs-on: ubuntu-latest 121 | steps: 122 | - uses: actions/checkout@v4 123 | - uses: streetsidesoftware/cspell-action@v6 124 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | seed_phrase 3 | contract_rs.wasm 4 | shell.nix 5 | .envrc 6 | .direnv 7 | Cargo.lock 8 | -------------------------------------------------------------------------------- /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.6.1](https://github.com/near/near-api-rs/compare/v0.6.0...v0.6.1) - 2025-07-05 11 | 12 | ### Fixed 13 | 14 | - Fixed compilation errors when latest near-sdk-rs is used ([#62](https://github.com/near/near-api-rs/pull/62)) 15 | 16 | ## [0.6.0](https://github.com/near/near-api-rs/compare/v0.5.0...v0.6.0) - 2025-05-16 17 | 18 | ### Added 19 | 20 | - [**breaking**] added support for the s Global Contracts (NEP-591) ([#56](https://github.com/near/near-api-rs/pull/56)) 21 | - [**breaking**] add field output_wasm_path to ContractSourceMetadata ([#55](https://github.com/near/near-api-rs/pull/55)) 22 | - add issues & prs to devtools project ([#52](https://github.com/near/near-api-rs/pull/52)) 23 | 24 | ### Fixed 25 | 26 | - allow forks to leverage transfer-to-project workflow ([#54](https://github.com/near/near-api-rs/pull/54)) 27 | 28 | ### Other 29 | 30 | - [**breaking**] updates near-* dependencies to 0.30 release ([#59](https://github.com/near/near-api-rs/pull/59)) 31 | - *(near-contract-standards)* deserialize ContractSourceMetadata::standards field so, as if it were optional 32 | 33 | ## [0.5.0](https://github.com/near/near-api-rs/compare/v0.4.0...v0.5.0) - 2025-03-16 34 | 35 | ### Added 36 | 37 | - added `map` method to query builders ([#45](https://github.com/near/near-api-rs/pull/45)) 38 | - *(types::contract)* add `BuildInfo` field to `ContractSourceMetadata` ([#46](https://github.com/near/near-api-rs/pull/46)) 39 | - [**breaking**] NEP-413 support ([#37](https://github.com/near/near-api-rs/pull/37)) 40 | 41 | ### Other 42 | 43 | - [**breaking**] updates near-* dependencies to 0.29 release ([#51](https://github.com/near/near-api-rs/pull/51)) 44 | - added rust backward compatibility job, updated project readme ([#48](https://github.com/near/near-api-rs/pull/48)) 45 | - [**breaking**] documented types ([#44](https://github.com/near/near-api-rs/pull/44)) 46 | - added cargo words to supported dictionary ([#43](https://github.com/near/near-api-rs/pull/43)) 47 | - [**breaking**] added spellcheck ([#42](https://github.com/near/near-api-rs/pull/42)) 48 | - [**breaking**] documented all the builders. API changes ([#39](https://github.com/near/near-api-rs/pull/39)) 49 | - documented network config ([#35](https://github.com/near/near-api-rs/pull/35)) 50 | 51 | ## [0.4.0](https://github.com/near/near-api-rs/compare/v0.3.0...v0.4.0) - 2024-12-19 52 | 53 | ### Added 54 | 55 | - added ability to specify backup rpc for connecting to the network (#28) 56 | - don't retry on critical errors (query, tx) (#27) 57 | 58 | ### Other 59 | 60 | - updates near-* dependencies to 0.28 release. Removed Cargo.lock (#33) 61 | - [**breaking**] added documentation for root level and signer module (#32) 62 | - added CODEOWNERS (#31) 63 | - removed prelude and filtered entries. (#29) 64 | - replaced SecretBuilder with utility functions (#26) 65 | - [**breaking**] replaced deploy method as a static method (#18) 66 | 67 | ## [0.3.0](https://github.com/near/near-api-rs/compare/v0.2.1...v0.3.0) - 2024-11-19 68 | 69 | ### Added 70 | - added querying block, block hash, and block number ([#9](https://github.com/near/near-api-rs/pull/9)) 71 | - added prelude module ([#9](https://github.com/near/near-api-rs/pull/9)) 72 | 73 | ### Other 74 | - [**breaking**] updated near-* dependencies to 0.27 release ([#13](https://github.com/near/near-api-rs/pull/13)) 75 | 76 | ## [0.2.1](https://github.com/near/near-api-rs/compare/v0.2.0...v0.2.1) - 2024-10-25 77 | 78 | ### Added 79 | 80 | - added retry to querying. Simplified retry logic. ([#7](https://github.com/near/near-api-rs/pull/7)) 81 | 82 | ### Other 83 | 84 | - Update README.md 85 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dj8yfo @akorchyn @PolyProgrammist 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "3" 3 | members = ["api", "types"] 4 | rust-version = "1.85" 5 | 6 | [workspace.package] 7 | edition = "2024" 8 | resolver = "3" 9 | license = "MIT OR Apache-2.0" 10 | repository = "https://github.com/polyprogrammist/near-openapi-client" 11 | 12 | 13 | [workspace.dependencies] 14 | near-api-types = { path = "types" } 15 | 16 | borsh = "1.5" 17 | async-trait = "0.1" 18 | base64 = "0.22" 19 | 20 | reqwest = "0.12" 21 | futures = "0.3" 22 | # Ad-hoc fix for compilation errors (rustls is used instead of openssl to ease the deployment avoiding the system dependency on openssl) 23 | openssl = { version = "0.10", default-features = false } 24 | 25 | bip39 = "2.0.0" 26 | serde = { version = "1.0", features = ["derive"], default-features = false } 27 | serde_json = "1.0.57" 28 | slipped10 = { version = "0.4.6" } 29 | url = { version = "2", features = ["serde"] } 30 | tokio = { version = "1.0", default-features = false } 31 | tracing = "0.1" 32 | thiserror = "1" 33 | zstd = "0.11" 34 | keyring = { version = "3.2", default-features = false } 35 | bs58 = "0.5" 36 | sha2 = "0.10" 37 | bolero = "0.13" 38 | 39 | primitive-types = { version = "0.10", default-features = false } 40 | ed25519-dalek = { version = "2.1.0", default-features = false, features = [] } 41 | secp256k1 = { version = "0.27.0", default-features = false, features = [ 42 | "recovery", 43 | "alloc", 44 | ] } 45 | 46 | near-account-id = { version = "1.1.0", features = ["serde", "borsh"] } 47 | near-gas = { version = "0.3", features = ["serde", "borsh"] } 48 | near-token = { version = "0.3", features = ["serde", "borsh"] } 49 | near-abi = "0.4.2" 50 | near-ledger = "0.9.1" 51 | near-openapi-client = "0.4.0" 52 | near-openapi-types = "0.4.0" 53 | 54 | # Dev-dependencies 55 | near-primitives = { version = "0.32" } 56 | near-crypto = { version = "0.32" } 57 | near-sandbox = { version = "0.2.0", features = [ 58 | "generate", 59 | ], default-features = false } 60 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # near-api 2 |

3 | Reference Documentation 4 | Crates.io version 5 | Download 6 | Join the community on Discord 7 | Join the community on Telegram 8 |

9 | 10 | The `near-api` is a simple Rust library that helps developers interact easily with the NEAR blockchain. The library was highly inspired by the API of the [`near-cli-rs`](https://github.com/near/near-cli-rs) library. The library extensively utilizes builder patterns, this way we guide the users through the user flow, preventing most of the errors and focusing on each step. 11 | 12 | Currently, the library provides: 13 | * Account management 14 | * Contract deployment and interaction 15 | * NEAR, FT, NFT transfers 16 | * Storage deposit management 17 | * Stake management 18 | * Ability to create custom transactions 19 | * Several ways to sign transactions (secret key, seed phrase, file, ledger, secure keychain). 20 | * Account key pool support to sign the transaction with different user keys to avoid nonce issues. 21 | 22 | The minimum required version is located in the [rust-version](./Cargo.toml#L4) field of the `Cargo.toml` file. 23 | 24 | ## Features 25 | 26 | * `ledger`: Enables integration with a Ledger hardware signer for secure key management. 27 | * `keystore`: Enables integration with a system keystore signer for managing keys securely on the local system. 28 | 29 | ## Current issues 30 | 31 | The library is in good condition, but lacks a few points to be even better: 32 | - [x] documentation 33 | - [ ] good quality examples 34 | - [ ] integration tests for all API calls 35 | - [x] CI 36 | - [x] anyhow -> thiserror 37 | - [x] ledger is blocking and it's not good in the async runtime 38 | - [ ] secure keychain is not that straightforward to use 39 | - [x] storage deposit manager for FT calls 40 | - [x] basic logging with tracing for querying/signing/sending transactions 41 | - [x] self-sustainable. remove the `nearcore` as a dependency ([#5](https://github.com/near/near-api-rs/issues/5)) 42 | 43 | ## Examples 44 | The crate provides [examples](./examples/) that contain detailed information on using the library. 45 | -------------------------------------------------------------------------------- /api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "near-api" 3 | version = "0.6.1" 4 | rust-version = "1.85" 5 | authors = [ 6 | "akorchyn ", 7 | "frol ", 8 | "Near Inc ", 9 | ] 10 | license = "MIT OR Apache-2.0" 11 | edition = "2024" 12 | resolver = "2" 13 | repository = "https://github.com/near/near-api-rs" 14 | description = "Rust library to interact with NEAR Protocol via RPC API" 15 | 16 | exclude = ["resources", "tests"] 17 | 18 | [package.metadata.docs.rs] 19 | all-features = true 20 | rustdoc-args = ["--document-private-items"] 21 | 22 | [dependencies] 23 | borsh.workspace = true 24 | async-trait.workspace = true 25 | base64.workspace = true 26 | 27 | # Remove this after https://github.com/near/near-sandbox-rs/pull/14 28 | chrono = { version = "0.4", features = ["now"], default-features = false } 29 | 30 | reqwest = { workspace = true, features = ["blocking", "json"] } 31 | futures.workspace = true 32 | # Ad-hoc fix for compilation errors (rustls is used instead of openssl to ease the deployment avoiding the system dependency on openssl) 33 | openssl = { workspace = true, features = ["vendored"] } 34 | 35 | bip39 = { workspace = true, features = ["rand"] } 36 | serde = { workspace = true, features = ["derive"] } 37 | serde_json.workspace = true 38 | slipped10.workspace = true 39 | url.workspace = true 40 | tokio = { workspace = true, default-features = false, features = ["time"] } 41 | tracing.workspace = true 42 | thiserror.workspace = true 43 | 44 | near-ledger = { workspace = true, optional = true } 45 | near-openapi-client.workspace = true 46 | near-api-types.workspace = true 47 | 48 | zstd.workspace = true 49 | 50 | keyring = { workspace = true, features = [ 51 | "apple-native", 52 | "windows-native", 53 | "sync-secret-service", 54 | "vendored", 55 | ], optional = true } 56 | 57 | 58 | [features] 59 | default = ["keystore", "ledger"] 60 | ledger = ["near-ledger"] 61 | keystore = ["dep:keyring"] 62 | 63 | [dev-dependencies] 64 | tokio = { workspace = true, default-features = false, features = ["full"] } 65 | near-sandbox = { workspace = true, features = ["generate"] } 66 | -------------------------------------------------------------------------------- /api/examples/account_key_pooling.rs: -------------------------------------------------------------------------------- 1 | /// You can use account key pooling to use different keys for consecutive transactions 2 | /// to avoid nonce-related issues. 3 | /// 4 | /// This is an example of how to use account key pooling to send multiple transactions 5 | /// using different keys. 6 | use near_api::{ 7 | Account, NetworkConfig, Signer, Tokens, 8 | signer::generate_secret_key, 9 | types::{AccessKeyPermission, AccountId, NearToken}, 10 | }; 11 | use near_sandbox::{ 12 | GenesisAccount, SandboxConfig, 13 | config::{DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY}, 14 | }; 15 | 16 | use std::sync::Arc; 17 | 18 | #[tokio::main] 19 | async fn main() { 20 | let account: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 21 | let second_account = GenesisAccount::generate_with_name("second_account".parse().unwrap()); 22 | 23 | let sandbox = near_sandbox::Sandbox::start_sandbox_with_config(SandboxConfig { 24 | additional_accounts: vec![second_account.clone()], 25 | ..Default::default() 26 | }) 27 | .await 28 | .unwrap(); 29 | let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse().unwrap()); 30 | let signer = Signer::new(Signer::from_secret_key( 31 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 32 | )) 33 | .unwrap(); 34 | 35 | println!( 36 | "Initial public key: {}", 37 | signer.get_public_key().await.unwrap() 38 | ); 39 | 40 | let secret_key = generate_secret_key().unwrap(); 41 | println!("New public key: {}", secret_key.public_key()); 42 | 43 | Account(account.clone()) 44 | .add_key(AccessKeyPermission::FullAccess, secret_key.public_key()) 45 | .with_signer(Arc::clone(&signer)) 46 | .send_to(&network) 47 | .await 48 | .unwrap() 49 | .assert_success(); 50 | 51 | signer 52 | .add_signer_to_pool(Signer::from_secret_key(secret_key)) 53 | .await 54 | .unwrap(); 55 | 56 | let txs = (0..2).map(|_| { 57 | Tokens::account(account.clone()) 58 | .send_to(second_account.account_id.clone()) 59 | .near(NearToken::from_millinear(1)) 60 | .with_signer(Arc::clone(&signer)) 61 | .send_to(&network) 62 | }); 63 | let results = futures::future::join_all(txs) 64 | .await 65 | .into_iter() 66 | .collect::, _>>() 67 | .unwrap(); 68 | 69 | assert_eq!(results.len(), 2); 70 | results.clone().into_iter().for_each(|e| { 71 | e.assert_success(); 72 | }); 73 | println!( 74 | "Transaction one public key: {}", 75 | results[0].transaction().public_key() 76 | ); 77 | println!( 78 | "Transaction two public key: {}", 79 | results[1].transaction().public_key() 80 | ); 81 | assert_ne!( 82 | results[0].transaction().public_key(), 83 | results[1].transaction().public_key() 84 | ); 85 | 86 | println!("All transactions are successful"); 87 | } 88 | -------------------------------------------------------------------------------- /api/examples/contract_source_metadata.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use near_api::{Contract, types::AccountId}; 4 | 5 | #[tokio::main] 6 | async fn main() { 7 | for (account_name, expected_json_metadata) in [ 8 | ("desolate-toad.testnet", FIRST_METADATA), 9 | ("fat-fabulous-toad.testnet", SECOND_METADATA), 10 | ] { 11 | let source_metadata = Contract(AccountId::from_str(account_name).expect("no err")) 12 | .contract_source_metadata() 13 | .fetch_from_testnet() 14 | .await 15 | .expect("no network or rpc err"); 16 | 17 | assert_eq!( 18 | expected_json_metadata, 19 | serde_json::to_string_pretty(&source_metadata.data).expect("no ser err") 20 | ); 21 | } 22 | } 23 | 24 | const FIRST_METADATA: &str = r#"{ 25 | "version": "0.1.0", 26 | "link": "https://github.com/dj8yfo/quiet_glen", 27 | "standards": [ 28 | { 29 | "standard": "nep330", 30 | "version": "1.2.0" 31 | } 32 | ], 33 | "build_info": null 34 | }"#; 35 | 36 | const SECOND_METADATA: &str = r#"{ 37 | "version": "0.1.0", 38 | "link": "https://github.com/dj8yfo/quiet_glen/tree/8d8a8a0fe86a1d8eb3bce45f04ab1a65fecf5a1b", 39 | "standards": [ 40 | { 41 | "standard": "nep330", 42 | "version": "1.2.0" 43 | } 44 | ], 45 | "build_info": { 46 | "build_environment": "sourcescan/cargo-near:0.13.3-rust-1.84.0@sha256:722198ddb92d1b82cbfcd3a4a9f7fba6fd8715f4d0b5fb236d8725c4883f97de", 47 | "build_command": [ 48 | "cargo", 49 | "near", 50 | "build", 51 | "non-reproducible-wasm", 52 | "--locked" 53 | ], 54 | "contract_path": "", 55 | "source_code_snapshot": "git+https://github.com/dj8yfo/quiet_glen?rev=8d8a8a0fe86a1d8eb3bce45f04ab1a65fecf5a1b", 56 | "output_wasm_path": null 57 | } 58 | }"#; 59 | -------------------------------------------------------------------------------- /api/examples/create_account_and_send_near.rs: -------------------------------------------------------------------------------- 1 | use near_api::{ 2 | Account, NetworkConfig, Signer, Tokens, 3 | signer::generate_secret_key, 4 | types::{AccountId, NearToken}, 5 | }; 6 | use near_sandbox::config::{DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY}; 7 | 8 | #[tokio::main] 9 | async fn main() { 10 | let network = near_sandbox::Sandbox::start_sandbox().await.unwrap(); 11 | 12 | let network = NetworkConfig::from_rpc_url("sandbox", network.rpc_addr.parse().unwrap()); 13 | let account: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 14 | let signer = Signer::new(Signer::from_secret_key( 15 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 16 | )) 17 | .unwrap(); 18 | 19 | let balance = Tokens::account(account.clone()) 20 | .near_balance() 21 | .fetch_from(&network) 22 | .await 23 | .unwrap(); 24 | 25 | println!("Balance: {}", balance.total); 26 | 27 | let new_account: AccountId = format!("{}.{}", "bob", account).parse().unwrap(); 28 | 29 | Account::create_account(new_account.clone()) 30 | .fund_myself(account.clone(), NearToken::from_near(1)) 31 | .public_key(generate_secret_key().unwrap().public_key()) 32 | .unwrap() 33 | .with_signer(signer.clone()) 34 | .send_to(&network) 35 | .await 36 | .unwrap() 37 | .assert_success(); 38 | 39 | Tokens::account(account.clone()) 40 | .send_to(new_account.clone()) 41 | .near(NearToken::from_near(1)) 42 | .with_signer(signer) 43 | .send_to(&network) 44 | .await 45 | .unwrap() 46 | .assert_success(); 47 | 48 | let new_account_balance = Tokens::account(account.clone()) 49 | .near_balance() 50 | .fetch_from(&network) 51 | .await 52 | .unwrap(); 53 | let bob_balance = Tokens::account(new_account) 54 | .near_balance() 55 | .fetch_from(&network) 56 | .await 57 | .unwrap(); 58 | 59 | println!("Balance: {}", new_account_balance.total); 60 | // Expect to see 2 NEAR in Bob's account. 1 NEAR from create_account and 1 NEAR from send_near 61 | println!("Bob balance: {}", bob_balance.total); 62 | } 63 | -------------------------------------------------------------------------------- /api/examples/deploy_and_call_method.rs: -------------------------------------------------------------------------------- 1 | use near_api::{ 2 | Contract, NetworkConfig, Signer, 3 | types::{AccountId, Data}, 4 | }; 5 | use near_sandbox::config::{DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY}; 6 | 7 | #[tokio::main] 8 | async fn main() { 9 | let network = near_sandbox::Sandbox::start_sandbox().await.unwrap(); 10 | let account: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 11 | let network = NetworkConfig::from_rpc_url("sandbox", network.rpc_addr.parse().unwrap()); 12 | 13 | let signer = Signer::new(Signer::from_secret_key( 14 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 15 | )) 16 | .unwrap(); 17 | 18 | // Let's deploy the contract. The contract is simple counter with `get_num`, `increase`, `decrease` arguments 19 | Contract::deploy(account.clone()) 20 | .use_code(include_bytes!("../resources/counter.wasm").to_vec()) 21 | // You can add init call as well using `with_init_call` 22 | .without_init_call() 23 | .with_signer(signer.clone()) 24 | .send_to(&network) 25 | .await 26 | .unwrap() 27 | .assert_success(); 28 | 29 | let contract = Contract(account.clone()); 30 | 31 | // Let's fetch current value on a contract 32 | let current_value: Data = contract 33 | // Please note that you can add any argument as long as it is deserializable by serde :) 34 | // feel free to use serde_json::json macro as well 35 | .call_function("get_num", ()) 36 | .unwrap() 37 | .read_only() 38 | .fetch_from(&network) 39 | .await 40 | .unwrap(); 41 | 42 | println!("Current value: {}", current_value.data); 43 | 44 | // Here is a transaction that require signing compared to view call that was used before. 45 | contract 46 | .call_function("increment", ()) 47 | .unwrap() 48 | .transaction() 49 | .with_signer(account.clone(), signer.clone()) 50 | .send_to(&network) 51 | .await 52 | .unwrap() 53 | .assert_success(); 54 | 55 | let current_value: Data = contract 56 | .call_function("get_num", ()) 57 | .unwrap() 58 | .read_only() 59 | .fetch_from(&network) 60 | .await 61 | .unwrap(); 62 | 63 | println!("Current value: {}", current_value.data); 64 | } 65 | -------------------------------------------------------------------------------- /api/examples/ft.rs: -------------------------------------------------------------------------------- 1 | use near_api::{ 2 | Contract, NetworkConfig, Signer, Tokens, 3 | types::{AccountId, tokens::FTBalance}, 4 | }; 5 | use near_sandbox::{GenesisAccount, SandboxConfig, config::DEFAULT_GENESIS_ACCOUNT}; 6 | 7 | use serde_json::json; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | let token = GenesisAccount::generate_with_name("token".parse().unwrap()); 12 | let account: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 13 | let token_signer = Signer::new(Signer::from_secret_key( 14 | token.private_key.clone().parse().unwrap(), 15 | )) 16 | .unwrap(); 17 | 18 | let sandbox = near_sandbox::Sandbox::start_sandbox_with_config(SandboxConfig { 19 | additional_accounts: vec![token.clone()], 20 | ..Default::default() 21 | }) 22 | .await 23 | .unwrap(); 24 | let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse().unwrap()); 25 | 26 | // Deploying token contract 27 | Contract::deploy(token.account_id.clone()) 28 | .use_code(include_bytes!("../resources/fungible_token.wasm").to_vec()) 29 | .with_init_call( 30 | "new_default_meta", 31 | json!({ 32 | "owner_id": token.account_id.clone(), 33 | "total_supply": "1000000000000000000000000000" 34 | }), 35 | ) 36 | .unwrap() 37 | .with_signer(token_signer.clone()) 38 | .send_to(&network) 39 | .await 40 | .unwrap() 41 | .assert_success(); 42 | 43 | // Verifying that user has 1000 tokens 44 | let tokens = Tokens::account(token.account_id.clone()) 45 | .ft_balance(token.account_id.clone()) 46 | .unwrap() 47 | .fetch_from(&network) 48 | .await 49 | .unwrap(); 50 | 51 | println!("Owner has {tokens}"); 52 | 53 | // Transfer 100 tokens to the account 54 | // We handle internally the storage deposit for the receiver account 55 | Tokens::account(token.account_id.clone()) 56 | .send_to(account.clone()) 57 | .ft( 58 | token.account_id.clone(), 59 | // Send 1.5 tokens 60 | FTBalance::with_decimals(24).with_whole_amount(100), 61 | ) 62 | .unwrap() 63 | .with_signer(token_signer.clone()) 64 | .send_to(&network) 65 | .await 66 | .unwrap() 67 | .assert_success(); 68 | 69 | let tokens = Tokens::account(account.clone()) 70 | .ft_balance(token.account_id.clone()) 71 | .unwrap() 72 | .fetch_from(&network) 73 | .await 74 | .unwrap(); 75 | 76 | println!("Account has {tokens}"); 77 | 78 | let tokens = Tokens::account(token.account_id.clone()) 79 | .ft_balance(token.account_id.clone()) 80 | .unwrap() 81 | .fetch_from(&network) 82 | .await 83 | .unwrap(); 84 | 85 | println!("Owner has {tokens}"); 86 | 87 | // We validate decimals at the network level so this should fail with a validation error 88 | let token = Tokens::account(token.account_id.clone()) 89 | .send_to(account.clone()) 90 | .ft( 91 | token.account_id.clone(), 92 | FTBalance::with_decimals(8).with_whole_amount(100), 93 | ) 94 | .unwrap() 95 | .with_signer(token_signer) 96 | .send_to(&network) 97 | .await; 98 | 99 | assert!(token.is_err()); 100 | println!( 101 | "Expected decimal validation error: {}", 102 | token.err().unwrap() 103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /api/examples/global_deploy.rs: -------------------------------------------------------------------------------- 1 | use near_api::{Contract, NetworkConfig, Signer, types::CryptoHash}; 2 | use near_sandbox::{GenesisAccount, SandboxConfig}; 3 | 4 | #[tokio::main] 5 | async fn main() { 6 | let global = GenesisAccount::generate_with_name("global".parse().unwrap()); 7 | let instance_of_global = 8 | GenesisAccount::generate_with_name("instance_of_global".parse().unwrap()); 9 | let sandbox = near_sandbox::Sandbox::start_sandbox_with_config(SandboxConfig { 10 | additional_accounts: vec![global.clone(), instance_of_global.clone()], 11 | ..Default::default() 12 | }) 13 | .await 14 | .unwrap(); 15 | let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse().unwrap()); 16 | 17 | let global_signer = Signer::new(Signer::from_secret_key( 18 | global.private_key.clone().parse().unwrap(), 19 | )) 20 | .unwrap(); 21 | let instance_of_global_signer = Signer::new(Signer::from_secret_key( 22 | instance_of_global.private_key.clone().parse().unwrap(), 23 | )) 24 | .unwrap(); 25 | 26 | let code: Vec = include_bytes!("../resources/counter.wasm").to_vec(); 27 | let contract_hash = CryptoHash::hash(&code); 28 | 29 | Contract::deploy_global_contract_code(code.clone()) 30 | .as_hash() 31 | .with_signer(global.account_id.clone(), global_signer.clone()) 32 | .send_to(&network) 33 | .await 34 | .unwrap() 35 | .assert_success(); 36 | 37 | Contract::deploy_global_contract_code(code) 38 | .as_account_id(global.account_id.clone()) 39 | .with_signer(global_signer.clone()) 40 | .send_to(&network) 41 | .await 42 | .unwrap() 43 | .assert_success(); 44 | 45 | Contract::deploy(instance_of_global.account_id.clone()) 46 | .use_global_account_id(global.account_id.clone()) 47 | .without_init_call() 48 | .with_signer(instance_of_global_signer.clone()) 49 | .send_to(&network) 50 | .await 51 | .unwrap() 52 | .assert_success(); 53 | 54 | Contract::deploy(instance_of_global.account_id.clone()) 55 | .use_global_hash(contract_hash) 56 | .without_init_call() 57 | .with_signer(instance_of_global_signer.clone()) 58 | .send_to(&network) 59 | .await 60 | .unwrap() 61 | .assert_success(); 62 | 63 | println!( 64 | "Successfully deployed contract using both global hash and global account ID methods!" 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /api/examples/nep_413_signing_message.rs: -------------------------------------------------------------------------------- 1 | use near_api::{Signer, SignerTrait}; 2 | 3 | use openssl::rand::rand_bytes; 4 | 5 | #[tokio::main] 6 | async fn main() { 7 | let signer = Signer::from_seed_phrase( 8 | "fatal edge jacket cash hard pass gallery fabric whisper size rain biology", 9 | None, 10 | ) 11 | .unwrap(); 12 | 13 | let mut nonce = [0u8; 32]; 14 | rand_bytes(&mut nonce).unwrap(); 15 | 16 | let payload = near_api::signer::NEP413Payload { 17 | message: "Hello NEAR!".to_string(), 18 | nonce, 19 | recipient: "example.near".to_string(), 20 | callback_url: None, 21 | }; 22 | 23 | let signature = signer 24 | .sign_message_nep413( 25 | "round-toad.testnet".parse().unwrap(), 26 | signer.get_public_key().unwrap(), 27 | payload, 28 | ) 29 | .await 30 | .unwrap(); 31 | 32 | println!("Signature: {signature}"); 33 | } 34 | -------------------------------------------------------------------------------- /api/examples/nft.rs: -------------------------------------------------------------------------------- 1 | use near_api::{ 2 | Contract, NetworkConfig, Signer, Tokens, 3 | types::{AccountId, NearToken, nft::TokenMetadata}, 4 | }; 5 | use near_sandbox::{ 6 | GenesisAccount, SandboxConfig, 7 | config::{DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY}, 8 | }; 9 | use serde_json::json; 10 | 11 | #[tokio::main] 12 | async fn main() { 13 | let nft = GenesisAccount::generate_with_name("nft".parse().unwrap()); 14 | let account: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 15 | let account2 = GenesisAccount::generate_with_name("account2".parse().unwrap()); 16 | 17 | let sandbox = near_sandbox::Sandbox::start_sandbox_with_config(SandboxConfig { 18 | additional_accounts: vec![nft.clone(), account2.clone()], 19 | ..Default::default() 20 | }) 21 | .await 22 | .unwrap(); 23 | let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse().unwrap()); 24 | 25 | let nft_signer = Signer::new(Signer::from_secret_key( 26 | nft.private_key.clone().parse().unwrap(), 27 | )) 28 | .unwrap(); 29 | let account_signer = Signer::new(Signer::from_secret_key( 30 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 31 | )) 32 | .unwrap(); 33 | 34 | // Deploying token contract 35 | Contract::deploy(nft.account_id.clone()) 36 | .use_code(include_bytes!("../resources/nft.wasm").to_vec()) 37 | .with_init_call( 38 | "new_default_meta", 39 | json!({ 40 | "owner_id": nft.account_id.to_string(), 41 | }), 42 | ) 43 | .unwrap() 44 | .with_signer(nft_signer.clone()) 45 | .send_to(&network) 46 | .await 47 | .unwrap() 48 | .assert_success(); 49 | 50 | let contract = Contract(nft.account_id.clone()); 51 | 52 | // Mint NFT via contract call 53 | contract 54 | .call_function( 55 | "nft_mint", 56 | json!({ 57 | "token_id": "1", 58 | "receiver_id": account.to_string(), 59 | "token_metadata": TokenMetadata { 60 | title: Some("My NFT".to_string()), 61 | description: Some("My first NFT".to_string()), 62 | ..Default::default() 63 | } 64 | }), 65 | ) 66 | .unwrap() 67 | .transaction() 68 | .deposit(NearToken::from_millinear(100)) 69 | .with_signer(nft.account_id.clone(), nft_signer.clone()) 70 | .send_to(&network) 71 | .await 72 | .unwrap() 73 | .assert_success(); 74 | 75 | // Verifying that account has our nft token 76 | let tokens = Tokens::account(account.clone()) 77 | .nft_assets(nft.account_id.clone()) 78 | .unwrap() 79 | .fetch_from(&network) 80 | .await 81 | .unwrap(); 82 | 83 | assert_eq!(tokens.data.len(), 1); 84 | println!("Account has {}", tokens.data.first().unwrap().token_id); 85 | 86 | Tokens::account(account.clone()) 87 | .send_to(account2.account_id.clone()) 88 | .nft(nft.account_id.clone(), "1".to_string()) 89 | .unwrap() 90 | .with_signer(account_signer.clone()) 91 | .send_to(&network) 92 | .await 93 | .unwrap() 94 | .assert_success(); 95 | 96 | // Verifying that account doesn't have nft anymore 97 | let tokens = Tokens::account(account.clone()) 98 | .nft_assets(nft.account_id.clone()) 99 | .unwrap() 100 | .fetch_from(&network) 101 | .await 102 | .unwrap(); 103 | 104 | assert!(tokens.data.is_empty()); 105 | 106 | let tokens = Tokens::account(account2.account_id.clone()) 107 | .nft_assets(nft.account_id.clone()) 108 | .unwrap() 109 | .fetch_from(&network) 110 | .await 111 | .unwrap(); 112 | 113 | assert_eq!(tokens.data.len(), 1); 114 | println!("account 2 has {}", tokens.data.first().unwrap().token_id); 115 | } 116 | -------------------------------------------------------------------------------- /api/examples/run_all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | # Run all examples 4 | for example in $(find . -name "*.rs" -type f); do 5 | example_name=$(basename $example .rs) 6 | 7 | echo "--------------------------------" 8 | echo "Running $example_name" 9 | echo "--------------------------------" 10 | CI=true cargo run --release --example $example_name 11 | echo "--------------------------------" 12 | done 13 | -------------------------------------------------------------------------------- /api/examples/sign_options.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use near_api::{ 4 | Account, NetworkConfig, PublicKey, Signer, SignerTrait, 5 | signer::generate_seed_phrase_with_passphrase, 6 | types::{AccessKeyPermission, AccountId}, 7 | }; 8 | use near_sandbox::config::{ 9 | DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY, 10 | DEFAULT_GENESIS_ACCOUNT_PUBLIC_KEY, 11 | }; 12 | 13 | #[tokio::main] 14 | async fn main() { 15 | let network = near_sandbox::Sandbox::start_sandbox().await.unwrap(); 16 | let account: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 17 | let network = NetworkConfig::from_rpc_url("sandbox", network.rpc_addr.parse().unwrap()); 18 | 19 | // Current secret key from workspace 20 | let (new_seed_phrase, public_key) = generate_seed_phrase_with_passphrase("smile").unwrap(); 21 | 22 | // Let's add new key and get the seed phrase 23 | Account(account.clone()) 24 | .add_key(AccessKeyPermission::FullAccess, public_key) 25 | .with_signer( 26 | Signer::new(Signer::from_secret_key( 27 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 28 | )) 29 | .unwrap(), 30 | ) 31 | .send_to(&network) 32 | .await 33 | .unwrap() 34 | .assert_success(); 35 | 36 | if std::env::var("CI").is_ok() { 37 | println!("Skipping ledger signing in CI"); 38 | } else { 39 | // Let's add ledger to the account with the new seed phrase 40 | let ledger = Signer::from_ledger(); 41 | let ledger_pubkey = ledger.get_public_key().unwrap(); 42 | Account(account.clone()) 43 | .add_key(AccessKeyPermission::FullAccess, ledger_pubkey) 44 | .with_signer( 45 | Signer::new(Signer::from_seed_phrase(&new_seed_phrase, Some("smile")).unwrap()) 46 | .unwrap(), 47 | ) 48 | .send_to(&network) 49 | .await 50 | .unwrap() 51 | .assert_success(); 52 | 53 | println!("Signing with ledger"); 54 | 55 | // Let's sign some tx with the ledger key 56 | Account(account.clone()) 57 | .delete_key(PublicKey::from_str(DEFAULT_GENESIS_ACCOUNT_PUBLIC_KEY).unwrap()) 58 | .with_signer(Signer::new(ledger).unwrap()) 59 | .send_to(&network) 60 | .await 61 | .unwrap() 62 | .assert_success(); 63 | } 64 | 65 | let keys = Account(account.clone()) 66 | .list_keys() 67 | .fetch_from(&network) 68 | .await 69 | .unwrap(); 70 | 71 | // Should contain 2 keys: new key from seed phrase, and ledger key 72 | println!("{keys:#?}"); 73 | assert_eq!(keys.data.len(), 2); 74 | } 75 | -------------------------------------------------------------------------------- /api/examples/specify_backup_rpc.rs: -------------------------------------------------------------------------------- 1 | use near_api::{Chain, NetworkConfig, RPCEndpoint, types::Reference}; 2 | 3 | #[tokio::main] 4 | async fn main() { 5 | let mut network = NetworkConfig::mainnet(); 6 | network.rpc_endpoints.push( 7 | RPCEndpoint::new("https://near.lava.build:443".parse().unwrap()) 8 | .with_retries(5) 9 | .with_api_key("some potential api key".to_string()), 10 | ); 11 | // Query latest block 12 | let _block = Chain::block() 13 | .at(Reference::Optimistic) 14 | .fetch_from_mainnet() 15 | .await 16 | .unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /api/examples/specifying_block.rs: -------------------------------------------------------------------------------- 1 | use near_api::{Chain, types::Reference}; 2 | 3 | #[tokio::main] 4 | async fn main() { 5 | // Query latest block 6 | let _block = Chain::block() 7 | .at(Reference::Optimistic) 8 | .fetch_from_mainnet() 9 | .await 10 | .unwrap(); 11 | 12 | let block_number = Chain::block_number().fetch_from_mainnet().await.unwrap(); 13 | let block_hash = Chain::block_hash().fetch_from_mainnet().await.unwrap(); 14 | 15 | let _block = Chain::block() 16 | .at(Reference::AtBlock(block_number)) 17 | .fetch_from_mainnet() 18 | .await 19 | .unwrap(); 20 | 21 | let _block = Chain::block() 22 | .at(Reference::AtBlockHash(block_hash)) 23 | .fetch_from_mainnet() 24 | .await 25 | .unwrap(); 26 | } 27 | -------------------------------------------------------------------------------- /api/resources/counter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near/near-api-rs/2ea82c42007074a2f83613dff059604e2548af45/api/resources/counter.wasm -------------------------------------------------------------------------------- /api/resources/fungible_token.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near/near-api-rs/2ea82c42007074a2f83613dff059604e2548af45/api/resources/fungible_token.wasm -------------------------------------------------------------------------------- /api/resources/nft.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near/near-api-rs/2ea82c42007074a2f83613dff059604e2548af45/api/resources/nft.wasm -------------------------------------------------------------------------------- /api/src/account/create.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use near_api_types::{ 4 | AccessKey, AccessKeyPermission, AccountId, Action, NearGas, NearToken, PublicKey, 5 | transaction::{ 6 | PrepopulateTransaction, 7 | actions::{AddKeyAction, CreateAccountAction, TransferAction}, 8 | }, 9 | }; 10 | use reqwest::Response; 11 | use serde_json::json; 12 | use url::Url; 13 | 14 | use crate::{ 15 | Contract, NetworkConfig, 16 | common::send::Transactionable, 17 | errors::{AccountCreationError, FaucetError, ValidationError}, 18 | transactions::{ConstructTransaction, TransactionWithSign}, 19 | }; 20 | 21 | #[derive(Clone, Debug)] 22 | pub struct CreateAccountBuilder { 23 | pub account_id: AccountId, 24 | } 25 | 26 | impl CreateAccountBuilder { 27 | /// Create an NEAR account and fund it by your own 28 | /// 29 | /// You can only create an sub-account of your own account or sub-account of the linkdrop account ([near](https://nearblocks.io/address/near) on mainnet , [testnet](https://testnet.nearblocks.io/address/testnet) on testnet) 30 | pub fn fund_myself( 31 | self, 32 | signer_account_id: AccountId, 33 | initial_balance: NearToken, 34 | ) -> PublicKeyProvider, AccountCreationError> 35 | { 36 | PublicKeyProvider::new(Box::new(move |public_key| { 37 | let (actions, receiver_id) = if self.account_id.is_sub_account_of(&signer_account_id) { 38 | ( 39 | vec![ 40 | Action::CreateAccount(CreateAccountAction {}), 41 | Action::Transfer(TransferAction { 42 | deposit: initial_balance, 43 | }), 44 | Action::AddKey(Box::new(AddKeyAction { 45 | public_key, 46 | access_key: AccessKey { 47 | nonce: 0.into(), 48 | permission: AccessKeyPermission::FullAccess, 49 | }, 50 | })), 51 | ], 52 | self.account_id.clone(), 53 | ) 54 | } else if let Some(linkdrop_account_id) = self.account_id.get_parent_account_id() { 55 | ( 56 | Contract(linkdrop_account_id.to_owned()) 57 | .call_function( 58 | "create_account", 59 | json!({ 60 | "new_account_id": self.account_id.to_string(), 61 | "new_public_key": public_key, 62 | }), 63 | )? 64 | .transaction() 65 | .gas(NearGas::from_tgas(30)) 66 | .deposit(initial_balance) 67 | .with_signer_account(signer_account_id.clone()) 68 | .prepopulated() 69 | .actions, 70 | linkdrop_account_id.to_owned(), 71 | ) 72 | } else { 73 | return Err(AccountCreationError::TopLevelAccountIsNotAllowed); 74 | }; 75 | 76 | let prepopulated = ConstructTransaction::new(signer_account_id, receiver_id) 77 | .add_actions(actions) 78 | .prepopulated(); 79 | 80 | Ok(TransactionWithSign { 81 | tx: CreateAccountFundMyselfTx { prepopulated }, 82 | }) 83 | })) 84 | } 85 | 86 | /// Create an account sponsored by faucet service 87 | /// 88 | /// This is a way to create an account without having to fund it. It works only on testnet. 89 | /// You can only create an sub-account of the [testnet](https://testnet.nearblocks.io/address/testnet) account 90 | pub fn sponsor_by_faucet_service(self) -> PublicKeyProvider { 91 | PublicKeyProvider::new(Box::new(move |public_key| { 92 | Ok(CreateAccountByFaucet { 93 | new_account_id: self.account_id, 94 | public_key, 95 | }) 96 | })) 97 | } 98 | } 99 | 100 | #[derive(Clone, Debug)] 101 | pub struct CreateAccountByFaucet { 102 | pub new_account_id: AccountId, 103 | pub public_key: PublicKey, 104 | } 105 | 106 | impl CreateAccountByFaucet { 107 | /// Sends the account creation request to the default testnet faucet service. 108 | /// 109 | /// The account will be created as a sub-account of the [testnet](https://testnet.nearblocks.io/address/testnet) account 110 | pub async fn send_to_testnet_faucet(self) -> Result { 111 | let testnet = NetworkConfig::testnet(); 112 | self.send_to_config_faucet(&testnet).await 113 | } 114 | 115 | /// Sends the account creation request to the faucet service specified in the network config. 116 | /// This way you can specify your own faucet service. 117 | /// 118 | /// The function sends the request in the following format: 119 | /// ```json 120 | /// { 121 | /// "newAccountId": "new_account_id", 122 | /// "newAccountPublicKey": "new_account_public_key" 123 | /// } 124 | /// ``` 125 | pub async fn send_to_config_faucet( 126 | self, 127 | config: &NetworkConfig, 128 | ) -> Result { 129 | let faucet_service_url = match &config.faucet_url { 130 | Some(url) => url, 131 | None => return Err(FaucetError::FaucetIsNotDefined(config.network_name.clone())), 132 | }; 133 | 134 | self.send_to_faucet(faucet_service_url).await 135 | } 136 | 137 | /// Sends the account creation request to the faucet service specified by the URL. 138 | /// 139 | /// The function sends the request in the following format: 140 | /// ```json 141 | /// { 142 | /// "newAccountId": "new_account_id", 143 | /// "newAccountPublicKey": "new_account_public_key" 144 | /// } 145 | /// ``` 146 | pub async fn send_to_faucet(self, url: &Url) -> Result { 147 | let mut data = std::collections::HashMap::new(); 148 | data.insert("newAccountId", self.new_account_id.to_string()); 149 | data.insert("newAccountPublicKey", self.public_key.to_string()); 150 | 151 | let client = reqwest::Client::new(); 152 | 153 | Ok(client.post(url.clone()).json(&data).send().await?) 154 | } 155 | } 156 | 157 | #[derive(Clone, Debug)] 158 | pub struct CreateAccountFundMyselfTx { 159 | prepopulated: PrepopulateTransaction, 160 | } 161 | 162 | #[async_trait::async_trait] 163 | impl Transactionable for CreateAccountFundMyselfTx { 164 | fn prepopulated(&self) -> PrepopulateTransaction { 165 | self.prepopulated.clone() 166 | } 167 | 168 | async fn validate_with_network(&self, network: &NetworkConfig) -> Result<(), ValidationError> { 169 | if self 170 | .prepopulated 171 | .receiver_id 172 | .is_sub_account_of(&self.prepopulated.signer_id) 173 | { 174 | return Ok(()); 175 | } 176 | 177 | match &network.linkdrop_account_id { 178 | Some(linkdrop) => { 179 | if &self.prepopulated.receiver_id != linkdrop { 180 | Err(AccountCreationError::AccountShouldBeSubAccountOfSignerOrLinkdrop)?; 181 | } 182 | } 183 | None => Err(AccountCreationError::LinkdropIsNotDefined)?, 184 | } 185 | 186 | Ok(()) 187 | } 188 | } 189 | 190 | pub type PublicKeyCallback = dyn FnOnce(PublicKey) -> Result; 191 | 192 | pub struct PublicKeyProvider { 193 | next_step: Box>, 194 | } 195 | 196 | impl PublicKeyProvider { 197 | pub const fn new(next_step: Box>) -> Self { 198 | Self { next_step } 199 | } 200 | 201 | pub fn public_key(self, pk: impl Into) -> Result { 202 | (self.next_step)(pk.into()) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /api/src/chain.rs: -------------------------------------------------------------------------------- 1 | use near_api_types::{BlockHeight, CryptoHash, Reference}; 2 | 3 | use crate::{ 4 | advanced::{AndThenHandler, block_rpc::SimpleBlockRpc}, 5 | common::query::{PostprocessHandler, RequestBuilder, RpcBlockHandler}, 6 | }; 7 | 8 | /// Chain-related interactions with the NEAR Protocol 9 | /// 10 | /// The [`Chain`] struct provides methods to interact with the NEAR blockchain 11 | /// 12 | /// # Examples 13 | /// 14 | /// ```rust,no_run 15 | /// use near_api::*; 16 | /// 17 | /// # async fn example() -> Result<(), Box> { 18 | /// let block_number = Chain::block_number().fetch_from_testnet().await?; 19 | /// println!("Current block number: {}", block_number); 20 | /// # Ok(()) 21 | /// # } 22 | /// ``` 23 | #[derive(Debug, Clone, Copy)] 24 | pub struct Chain; 25 | 26 | impl Chain { 27 | /// Set ups a query to fetch the [BlockHeight] of the current block 28 | /// 29 | /// ## Fetching the latest block number 30 | /// 31 | /// ```rust,no_run 32 | /// use near_api::*; 33 | /// 34 | /// # async fn example() -> Result<(), Box> { 35 | /// let block_number = Chain::block_number().fetch_from_testnet().await?; 36 | /// println!("Current block number: {}", block_number); 37 | /// # Ok(()) 38 | /// # } 39 | /// ``` 40 | /// 41 | /// ## Fetching the final block number 42 | /// 43 | /// ```rust,no_run 44 | /// use near_api::*; 45 | /// 46 | /// # async fn example() -> Result<(), Box> { 47 | /// let block_number = Chain::block_number().at(Reference::Final).fetch_from_testnet().await?; 48 | /// println!("Final block number: {}", block_number); 49 | /// # Ok(()) 50 | /// # } 51 | /// ``` 52 | pub fn block_number() -> RequestBuilder> { 53 | RequestBuilder::new(SimpleBlockRpc, Reference::Optimistic, RpcBlockHandler) 54 | .map(|data| data.header.height) 55 | } 56 | 57 | /// Set ups a query to fetch the [CryptoHash] of the block 58 | /// 59 | /// ## Fetching the latest block hash 60 | /// 61 | /// ```rust,no_run 62 | /// use near_api::*; 63 | /// 64 | /// # async fn example() -> Result<(), Box> { 65 | /// let block_hash = Chain::block_hash().fetch_from_testnet().await?; 66 | /// println!("Current block hash: {}", block_hash); 67 | /// # Ok(()) 68 | /// # } 69 | /// ``` 70 | /// 71 | /// ## Fetching the hash at a specific block number 72 | /// 73 | /// ```rust,no_run 74 | /// use near_api::*; 75 | /// 76 | /// # async fn example() -> Result<(), Box> { 77 | /// let block_hash = Chain::block_hash().at(Reference::AtBlock(1000000)).fetch_from_testnet().await?; 78 | /// println!("Block hash at block number 1000000: {}", block_hash); 79 | /// # Ok(()) 80 | /// # } 81 | /// ``` 82 | pub fn block_hash() -> RequestBuilder> { 83 | RequestBuilder::new(SimpleBlockRpc, Reference::Optimistic, RpcBlockHandler) 84 | .and_then(|data| Ok(CryptoHash::try_from(data.header.hash)?)) 85 | } 86 | 87 | /// Set ups a query to fetch the [RpcBlockResponse][near_api_types::RpcBlockResponse] 88 | /// 89 | /// ## Fetching the latest block 90 | /// 91 | /// ```rust,no_run 92 | /// use near_api::*; 93 | /// 94 | /// # async fn example() -> Result<(), Box> { 95 | /// let block = Chain::block().fetch_from_testnet().await?; 96 | /// println!("Current block: {:?}", block); 97 | /// # Ok(()) 98 | /// # } 99 | /// ``` 100 | /// 101 | /// ## Fetching the block at a specific block number 102 | /// 103 | /// ```rust,no_run 104 | /// use near_api::*; 105 | /// 106 | /// # async fn example() -> Result<(), Box> { 107 | /// let block = Chain::block().at(Reference::AtBlock(1000000)).fetch_from_testnet().await?; 108 | /// println!("Block at block number 1000000: {:?}", block); 109 | /// # Ok(()) 110 | /// # } 111 | /// ``` 112 | /// 113 | /// ## Fetching the block at a specific block hash 114 | /// 115 | /// ```rust,no_run 116 | /// use near_api::*; 117 | /// 118 | /// # async fn example() -> Result<(), Box> { 119 | /// # let block_hash = near_api::types::CryptoHash::default(); 120 | /// let block = Chain::block().at(Reference::AtBlockHash(block_hash)).fetch_from_testnet().await?; 121 | /// println!("Block at block hash: {:?}", block); 122 | /// # Ok(()) 123 | /// # } 124 | /// ``` 125 | pub fn block() -> RequestBuilder { 126 | RequestBuilder::new(SimpleBlockRpc, Reference::Optimistic, RpcBlockHandler) 127 | } 128 | 129 | // TODO: chunk info 130 | } 131 | -------------------------------------------------------------------------------- /api/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | const META_TRANSACTION_VALID_FOR_DEFAULT: near_api_types::BlockHeight = 1000; 2 | 3 | pub mod query; 4 | pub mod send; 5 | pub mod utils; 6 | -------------------------------------------------------------------------------- /api/src/common/query/block_rpc.rs: -------------------------------------------------------------------------------- 1 | use near_api_types::Reference; 2 | use near_openapi_client::Client; 3 | use near_openapi_client::types::{ 4 | BlockId, Finality, JsonRpcRequestForBlock, JsonRpcRequestForBlockMethod, 5 | JsonRpcResponseForRpcBlockResponseAndRpcError, RpcBlockRequest, RpcBlockResponse, RpcError, 6 | }; 7 | 8 | use crate::{ 9 | NetworkConfig, advanced::RpcType, common::utils::is_critical_blocks_error, 10 | config::RetryResponse, errors::SendRequestError, 11 | }; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct SimpleBlockRpc; 15 | 16 | #[async_trait::async_trait] 17 | impl RpcType for SimpleBlockRpc { 18 | type RpcReference = Reference; 19 | type Response = RpcBlockResponse; 20 | type Error = RpcError; 21 | async fn send_query( 22 | &self, 23 | client: &Client, 24 | _network: &NetworkConfig, 25 | reference: &Reference, 26 | ) -> RetryResponse> { 27 | let request = match reference { 28 | Reference::Optimistic => RpcBlockRequest::Finality(Finality::Optimistic), 29 | Reference::NearFinal => RpcBlockRequest::Finality(Finality::NearFinal), 30 | Reference::Final => RpcBlockRequest::Finality(Finality::Final), 31 | Reference::AtBlock(block) => RpcBlockRequest::BlockId(BlockId::BlockHeight(*block)), 32 | Reference::AtBlockHash(block_hash) => { 33 | RpcBlockRequest::BlockId(BlockId::CryptoHash((*block_hash).into())) 34 | } 35 | }; 36 | let response = client 37 | .block(&JsonRpcRequestForBlock { 38 | id: "0".to_string(), 39 | jsonrpc: "2.0".to_string(), 40 | method: JsonRpcRequestForBlockMethod::Block, 41 | params: request, 42 | }) 43 | .await 44 | .map(|r| r.into_inner()); 45 | match response { 46 | Ok(JsonRpcResponseForRpcBlockResponseAndRpcError::Variant0 { result, .. }) => { 47 | RetryResponse::Ok(result) 48 | } 49 | Ok(JsonRpcResponseForRpcBlockResponseAndRpcError::Variant1 { error, .. }) => { 50 | if is_critical_blocks_error(&error) { 51 | RetryResponse::Critical(SendRequestError::ServerError(error)) 52 | } else { 53 | RetryResponse::Retry(SendRequestError::ServerError(error)) 54 | } 55 | } 56 | Err(err) => RetryResponse::Critical(SendRequestError::ClientError(err)), 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /api/src/common/query/handlers/transformers.rs: -------------------------------------------------------------------------------- 1 | use tracing::trace; 2 | 3 | use crate::{ 4 | advanced::{RpcType, handlers::ResponseHandler}, 5 | common::query::{QUERY_EXECUTOR_TARGET, ResultWithMethod}, 6 | errors::QueryError, 7 | }; 8 | 9 | #[derive(Clone, Debug)] 10 | pub struct MultiQueryHandler { 11 | handlers: Handlers, 12 | } 13 | 14 | impl ResponseHandler for MultiQueryHandler<(H1, H2)> 15 | where 16 | Query: RpcType, 17 | H1: ResponseHandler, 18 | H2: ResponseHandler, 19 | { 20 | type Response = (R1, R2); 21 | type Query = H1::Query; 22 | 23 | fn process_response( 24 | &self, 25 | mut responses: Vec<::Response>, 26 | ) -> ResultWithMethod::Error> { 27 | let (h1, h2) = &self.handlers; 28 | 29 | let first_response = 30 | h1.process_response(responses.drain(0..h1.request_amount()).collect())?; 31 | let second_response = h2.process_response(responses)?; 32 | 33 | Ok((first_response, second_response)) 34 | } 35 | 36 | fn request_amount(&self) -> usize { 37 | self.handlers.0.request_amount() + self.handlers.1.request_amount() 38 | } 39 | } 40 | 41 | impl ResponseHandler for MultiQueryHandler<(H1, H2, H3)> 42 | where 43 | Query: RpcType, 44 | H1: ResponseHandler, 45 | H2: ResponseHandler, 46 | H3: ResponseHandler, 47 | { 48 | type Response = (R1, R2, R3); 49 | type Query = Query; 50 | 51 | fn process_response( 52 | &self, 53 | mut responses: Vec<::Response>, 54 | ) -> ResultWithMethod::Error> { 55 | let (h1, h2, h3) = &self.handlers; 56 | 57 | let first_response = 58 | h1.process_response(responses.drain(0..h1.request_amount()).collect())?; 59 | let second_response = h2.process_response( 60 | responses 61 | .drain(h1.request_amount()..h2.request_amount()) 62 | .collect(), 63 | )?; 64 | let third_response = h3.process_response(responses)?; 65 | 66 | Ok((first_response, second_response, third_response)) 67 | } 68 | 69 | fn request_amount(&self) -> usize { 70 | self.handlers.0.request_amount() + self.handlers.1.request_amount() 71 | } 72 | } 73 | 74 | impl MultiQueryHandler { 75 | pub const fn new(handlers: Handlers) -> Self { 76 | Self { handlers } 77 | } 78 | } 79 | 80 | impl Default for MultiQueryHandler { 81 | fn default() -> Self { 82 | Self::new(Default::default()) 83 | } 84 | } 85 | 86 | pub struct PostprocessHandler { 87 | post_process: Box PostProcessed + Send + Sync>, 88 | handler: Handler, 89 | } 90 | 91 | impl PostprocessHandler { 92 | pub fn new(handler: Handler, post_process: F) -> Self 93 | where 94 | F: Fn(Handler::Response) -> PostProcessed + Send + Sync + 'static, 95 | { 96 | Self { 97 | post_process: Box::new(post_process), 98 | handler, 99 | } 100 | } 101 | } 102 | 103 | impl ResponseHandler for PostprocessHandler 104 | where 105 | Handler: ResponseHandler, 106 | { 107 | type Response = PostProcessed; 108 | type Query = Handler::Query; 109 | 110 | fn process_response( 111 | &self, 112 | response: Vec<::Response>, 113 | ) -> ResultWithMethod::Error> { 114 | trace!(target: QUERY_EXECUTOR_TARGET, "Processing response with postprocessing, response count: {}", response.len()); 115 | Handler::process_response(&self.handler, response).map(|data| { 116 | trace!(target: QUERY_EXECUTOR_TARGET, "Applying postprocessing"); 117 | (self.post_process)(data) 118 | }) 119 | } 120 | 121 | fn request_amount(&self) -> usize { 122 | self.handler.request_amount() 123 | } 124 | } 125 | 126 | pub struct AndThenHandler { 127 | #[allow(clippy::complexity)] 128 | post_process: Box< 129 | dyn Fn(Handler::Response) -> Result> 130 | + Send 131 | + Sync, 132 | >, 133 | handler: Handler, 134 | } 135 | 136 | impl AndThenHandler { 137 | pub fn new(handler: Handler, post_process: F) -> Self 138 | where 139 | F: Fn(Handler::Response) -> Result> 140 | + Send 141 | + Sync 142 | + 'static, 143 | { 144 | Self { 145 | post_process: Box::new(post_process), 146 | handler, 147 | } 148 | } 149 | } 150 | 151 | impl ResponseHandler for AndThenHandler 152 | where 153 | Handler: ResponseHandler, 154 | { 155 | type Response = PostProcessed; 156 | type Query = Handler::Query; 157 | 158 | fn process_response( 159 | &self, 160 | response: Vec<::Response>, 161 | ) -> ResultWithMethod::Error> { 162 | Handler::process_response(&self.handler, response) 163 | .map(|data| (self.post_process)(data))? 164 | .map_err(QueryError::ConversionError) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /api/src/common/query/query_rpc.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use near_openapi_client::types::{ 3 | JsonRpcRequestForQuery, JsonRpcRequestForQueryMethod, 4 | JsonRpcResponseForRpcQueryResponseAndRpcError, RpcError, RpcQueryResponse, 5 | }; 6 | 7 | use crate::{ 8 | NetworkConfig, 9 | advanced::{RpcType, query_request::QueryRequest}, 10 | common::utils::is_critical_query_error, 11 | config::RetryResponse, 12 | errors::SendRequestError, 13 | }; 14 | use near_api_types::Reference; 15 | 16 | #[derive(Clone, Debug)] 17 | pub struct SimpleQueryRpc { 18 | pub request: QueryRequest, 19 | } 20 | 21 | #[async_trait] 22 | impl RpcType for SimpleQueryRpc { 23 | type RpcReference = Reference; 24 | type Response = RpcQueryResponse; 25 | type Error = RpcError; 26 | async fn send_query( 27 | &self, 28 | client: &near_openapi_client::Client, 29 | _network: &NetworkConfig, 30 | reference: &Reference, 31 | ) -> RetryResponse> { 32 | let request = self.request.clone().to_rpc_query_request(reference.clone()); 33 | let response = client 34 | .query(&JsonRpcRequestForQuery { 35 | id: "0".to_string(), 36 | jsonrpc: "2.0".to_string(), 37 | method: JsonRpcRequestForQueryMethod::Query, 38 | params: request, 39 | }) 40 | .await 41 | .map(|r| r.into_inner()); 42 | match response { 43 | Ok(JsonRpcResponseForRpcQueryResponseAndRpcError::Variant0 { result, .. }) => { 44 | RetryResponse::Ok(result) 45 | } 46 | Ok(JsonRpcResponseForRpcQueryResponseAndRpcError::Variant1 { error, .. }) => { 47 | if is_critical_query_error(&error) { 48 | RetryResponse::Critical(SendRequestError::ServerError(error)) 49 | } else { 50 | RetryResponse::Retry(SendRequestError::ServerError(error)) 51 | } 52 | } 53 | Err(err) => RetryResponse::Critical(SendRequestError::ClientError(err)), 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /api/src/common/query/validator_rpc.rs: -------------------------------------------------------------------------------- 1 | use near_api_types::EpochReference; 2 | use near_openapi_client::Client; 3 | use near_openapi_client::types::{ 4 | BlockId, EpochId, JsonRpcRequestForValidators, JsonRpcRequestForValidatorsMethod, 5 | JsonRpcResponseForRpcValidatorResponseAndRpcError, RpcError, RpcValidatorRequest, 6 | RpcValidatorResponse, 7 | }; 8 | 9 | use crate::{ 10 | NetworkConfig, advanced::RpcType, common::utils::is_critical_validator_error, 11 | config::RetryResponse, errors::SendRequestError, 12 | }; 13 | 14 | #[derive(Clone, Debug)] 15 | pub struct SimpleValidatorRpc; 16 | 17 | #[async_trait::async_trait] 18 | impl RpcType for SimpleValidatorRpc { 19 | type RpcReference = EpochReference; 20 | type Response = RpcValidatorResponse; 21 | type Error = RpcError; 22 | async fn send_query( 23 | &self, 24 | client: &Client, 25 | _network: &NetworkConfig, 26 | reference: &EpochReference, 27 | ) -> RetryResponse> { 28 | let request = match reference { 29 | EpochReference::Latest => RpcValidatorRequest::Latest, 30 | EpochReference::AtEpoch(epoch) => { 31 | RpcValidatorRequest::EpochId(EpochId((*epoch).into())) 32 | } 33 | EpochReference::AtBlock(block) => { 34 | RpcValidatorRequest::BlockId(BlockId::BlockHeight(*block)) 35 | } 36 | EpochReference::AtBlockHash(block_hash) => { 37 | RpcValidatorRequest::BlockId(BlockId::CryptoHash((*block_hash).into())) 38 | } 39 | }; 40 | let response = client 41 | .validators(&JsonRpcRequestForValidators { 42 | id: "0".to_string(), 43 | jsonrpc: "2.0".to_string(), 44 | method: JsonRpcRequestForValidatorsMethod::Validators, 45 | params: request, 46 | }) 47 | .await 48 | .map(|r| r.into_inner()); 49 | match response { 50 | Ok(JsonRpcResponseForRpcValidatorResponseAndRpcError::Variant0 { result, .. }) => { 51 | RetryResponse::Ok(result) 52 | } 53 | Ok(JsonRpcResponseForRpcValidatorResponseAndRpcError::Variant1 { error, .. }) => { 54 | if is_critical_validator_error(&error) { 55 | RetryResponse::Critical(SendRequestError::ServerError(error)) 56 | } else { 57 | RetryResponse::Retry(SendRequestError::ServerError(error)) 58 | } 59 | } 60 | Err(err) => RetryResponse::Critical(SendRequestError::ClientError(err)), 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /api/src/common/utils.rs: -------------------------------------------------------------------------------- 1 | // https://github.com/near/near-token-rs/blob/3feafec624e7d1028ed00695f2acf87e1d823fa7/src/utils.rs#L1-L49 2 | 3 | use base64::{Engine, prelude::BASE64_STANDARD}; 4 | use near_api_types::NearToken; 5 | use near_openapi_client::types::RpcError; 6 | 7 | pub fn to_base64(input: &[u8]) -> String { 8 | BASE64_STANDARD.encode(input) 9 | } 10 | 11 | pub fn from_base64(encoded: &str) -> Result, base64::DecodeError> { 12 | BASE64_STANDARD.decode(encoded) 13 | } 14 | 15 | /// Converts [crate::Data]<[u128]>] to [crate::NearToken]. 16 | pub const fn near_data_to_near_token(data: near_api_types::Data) -> NearToken { 17 | NearToken::from_yoctonear(data.data) 18 | } 19 | 20 | // TODO: this is a temporary solution to check if an error is critical 21 | // we had previously a full scale support for that 22 | // but auto generated code doesn't support errors yet, so we would need to leave it as is for now 23 | // We default to false as we can't know if an error is critical or not without the types 24 | // so to keep it safe it's better to retry 25 | 26 | pub fn is_critical_blocks_error(err: &RpcError) -> bool { 27 | is_critical_json_rpc_error(err, |_| false) 28 | } 29 | 30 | pub fn is_critical_validator_error(err: &RpcError) -> bool { 31 | is_critical_json_rpc_error(err, |_| false) 32 | } 33 | 34 | pub fn is_critical_query_error(rpc_error: &RpcError) -> bool { 35 | is_critical_json_rpc_error(rpc_error, |_| false) 36 | } 37 | 38 | pub fn is_critical_transaction_error(err: &RpcError) -> bool { 39 | is_critical_json_rpc_error(err, |_| false) 40 | } 41 | 42 | fn is_critical_json_rpc_error( 43 | err: &RpcError, 44 | is_critical_t: impl Fn(&serde_json::Value) -> bool, 45 | ) -> bool { 46 | match err { 47 | RpcError::Variant0 { .. } => true, 48 | RpcError::Variant1 { cause, .. } => is_critical_t(cause), 49 | RpcError::Variant2 { .. } => false, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /api/src/config.rs: -------------------------------------------------------------------------------- 1 | use near_api_types::AccountId; 2 | use near_openapi_client::Client; 3 | use reqwest::header::{HeaderValue, InvalidHeaderValue}; 4 | use url::Url; 5 | 6 | use crate::errors::RetryError; 7 | 8 | #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] 9 | /// Specifies the retry strategy for RPC endpoint requests. 10 | pub enum RetryMethod { 11 | /// Exponential backoff strategy with configurable initial delay and multiplication factor. 12 | /// The delay is calculated as: `initial_sleep * factor^retry_number` 13 | ExponentialBackoff { 14 | /// The initial delay duration before the first retry 15 | initial_sleep: std::time::Duration, 16 | /// The multiplication factor for calculating subsequent delays 17 | factor: u8, 18 | }, 19 | /// Fixed delay strategy with constant sleep duration 20 | Fixed { 21 | /// The constant delay duration between retries 22 | sleep: std::time::Duration, 23 | }, 24 | } 25 | 26 | #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] 27 | /// Configuration for a [NEAR RPC](https://docs.near.org/api/rpc/providers) endpoint with retry and backoff settings. 28 | pub struct RPCEndpoint { 29 | /// The URL of the RPC endpoint 30 | pub url: url::Url, 31 | /// Optional API key for authenticated requests 32 | pub bearer_header: Option, 33 | /// Number of consecutive failures to move on to the next endpoint. 34 | pub retries: u8, 35 | /// The retry method to use 36 | pub retry_method: RetryMethod, 37 | } 38 | 39 | impl RPCEndpoint { 40 | /// Constructs a new RPC endpoint configuration with default settings. 41 | /// 42 | /// The default retry method is `ExponentialBackoff` with an initial sleep of 10ms and a factor of 2. 43 | /// The delays will be 10ms, 20ms, 40ms, 80ms, 160ms. 44 | pub const fn new(url: url::Url) -> Self { 45 | Self { 46 | url, 47 | bearer_header: None, 48 | retries: 5, 49 | // 10ms, 20ms, 40ms, 80ms, 160ms 50 | retry_method: RetryMethod::ExponentialBackoff { 51 | initial_sleep: std::time::Duration::from_millis(10), 52 | factor: 2, 53 | }, 54 | } 55 | } 56 | 57 | /// Constructs default mainnet configuration. 58 | pub fn mainnet() -> Self { 59 | Self::new("https://free.rpc.fastnear.com".parse().unwrap()) 60 | } 61 | 62 | /// Constructs default testnet configuration. 63 | pub fn testnet() -> Self { 64 | Self::new("https://test.rpc.fastnear.com".parse().unwrap()) 65 | } 66 | 67 | /// Set API key for the endpoint. 68 | pub fn with_api_key(mut self, api_key: String) -> Self { 69 | self.bearer_header = Some(format!("Bearer {api_key}")); 70 | self 71 | } 72 | 73 | /// Set number of retries for the endpoint before moving on to the next one. 74 | pub const fn with_retries(mut self, retries: u8) -> Self { 75 | self.retries = retries; 76 | self 77 | } 78 | 79 | pub const fn with_retry_method(mut self, retry_method: RetryMethod) -> Self { 80 | self.retry_method = retry_method; 81 | self 82 | } 83 | 84 | pub fn get_sleep_duration(&self, retry: usize) -> std::time::Duration { 85 | match self.retry_method { 86 | RetryMethod::ExponentialBackoff { 87 | initial_sleep, 88 | factor, 89 | } => initial_sleep * ((factor as u32).pow(retry as u32)), 90 | RetryMethod::Fixed { sleep } => sleep, 91 | } 92 | } 93 | } 94 | 95 | #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] 96 | /// Configuration for a NEAR network including RPC endpoints and network-specific settings. 97 | /// 98 | /// # Multiple RPC endpoints 99 | /// 100 | /// This struct is used to configure multiple RPC endpoints for a NEAR network. 101 | /// It allows for failover between endpoints in case of a failure. 102 | /// 103 | /// 104 | /// ## Example 105 | /// ```rust,no_run 106 | /// use near_api::*; 107 | /// 108 | /// # async fn example() -> Result<(), Box> { 109 | /// let config = NetworkConfig { 110 | /// rpc_endpoints: vec![RPCEndpoint::mainnet(), RPCEndpoint::new("https://near.lava.build".parse()?)], 111 | /// ..NetworkConfig::mainnet() 112 | /// }; 113 | /// # Ok(()) 114 | /// # } 115 | /// ``` 116 | pub struct NetworkConfig { 117 | /// Human readable name of the network (e.g. "mainnet", "testnet") 118 | pub network_name: String, 119 | /// List of [RPC endpoints](https://docs.near.org/api/rpc/providers) to use with failover 120 | pub rpc_endpoints: Vec, 121 | /// Account ID used for [linkdrop functionality](https://docs.near.org/build/primitives/linkdrop) 122 | pub linkdrop_account_id: Option, 123 | /// Account ID of the [NEAR Social contract](https://docs.near.org/social/contract) 124 | pub near_social_db_contract_account_id: Option, 125 | /// URL of the network's faucet service 126 | pub faucet_url: Option, 127 | /// URL for the [meta transaction relayer](https://docs.near.org/concepts/abstraction/relayers) service 128 | pub meta_transaction_relayer_url: Option, 129 | /// URL for the [fastnear](https://docs.near.org/tools/ecosystem-apis/fastnear-api) service. 130 | /// 131 | /// Currently, unused. See [#30](https://github.com/near/near-api-rs/issues/30) 132 | pub fastnear_url: Option, 133 | /// Account ID of the [staking pools factory](https://github.com/NearSocial/social-db) 134 | pub staking_pools_factory_account_id: Option, 135 | } 136 | 137 | impl NetworkConfig { 138 | /// Constructs default mainnet configuration. 139 | pub fn mainnet() -> Self { 140 | Self { 141 | network_name: "mainnet".to_string(), 142 | rpc_endpoints: vec![RPCEndpoint::mainnet()], 143 | linkdrop_account_id: Some("near".parse().unwrap()), 144 | near_social_db_contract_account_id: Some("social.near".parse().unwrap()), 145 | faucet_url: None, 146 | meta_transaction_relayer_url: None, 147 | fastnear_url: Some("https://api.fastnear.com/".parse().unwrap()), 148 | staking_pools_factory_account_id: Some("poolv1.near".parse().unwrap()), 149 | } 150 | } 151 | 152 | /// Constructs default testnet configuration. 153 | pub fn testnet() -> Self { 154 | Self { 155 | network_name: "testnet".to_string(), 156 | rpc_endpoints: vec![RPCEndpoint::testnet()], 157 | linkdrop_account_id: Some("testnet".parse().unwrap()), 158 | near_social_db_contract_account_id: Some("v1.social08.testnet".parse().unwrap()), 159 | faucet_url: Some("https://helper.nearprotocol.com/account".parse().unwrap()), 160 | meta_transaction_relayer_url: None, 161 | fastnear_url: None, 162 | staking_pools_factory_account_id: Some("pool.f863973.m0".parse().unwrap()), 163 | } 164 | } 165 | 166 | pub fn from_rpc_url(name: &str, rpc_url: Url) -> Self { 167 | Self { 168 | network_name: name.to_string(), 169 | rpc_endpoints: vec![RPCEndpoint::new(rpc_url)], 170 | linkdrop_account_id: None, 171 | near_social_db_contract_account_id: None, 172 | faucet_url: None, 173 | fastnear_url: None, 174 | meta_transaction_relayer_url: None, 175 | staking_pools_factory_account_id: None, 176 | } 177 | } 178 | 179 | pub(crate) fn client(&self, index: usize) -> Result { 180 | let rpc_endpoint = &self.rpc_endpoints[index]; 181 | 182 | let dur = std::time::Duration::from_secs(15); 183 | let mut client = reqwest::ClientBuilder::new() 184 | .connect_timeout(dur) 185 | .timeout(dur); 186 | 187 | if let Some(rpc_api_key) = &rpc_endpoint.bearer_header { 188 | let mut headers = reqwest::header::HeaderMap::new(); 189 | 190 | let mut header = HeaderValue::from_str(rpc_api_key)?; 191 | header.set_sensitive(true); 192 | 193 | headers.insert( 194 | reqwest::header::HeaderName::from_static("x-api-key"), 195 | header, 196 | ); 197 | client = client.default_headers(headers); 198 | }; 199 | Ok(near_openapi_client::Client::new_with_client( 200 | rpc_endpoint.url.as_ref().trim_end_matches('/'), 201 | client.build().unwrap(), 202 | )) 203 | } 204 | } 205 | 206 | #[derive(Debug)] 207 | /// Represents the possible outcomes of a retry-able operation. 208 | pub enum RetryResponse { 209 | /// Operation succeeded with result R 210 | Ok(R), 211 | /// Operation failed with error E, should be retried 212 | Retry(E), 213 | /// Operation failed with critical error E, should not be retried 214 | Critical(E), 215 | } 216 | 217 | impl From> for RetryResponse { 218 | fn from(value: Result) -> Self { 219 | match value { 220 | Ok(value) => Self::Ok(value), 221 | Err(value) => Self::Retry(value), 222 | } 223 | } 224 | } 225 | 226 | /// Retry a task with exponential backoff and failover. 227 | /// 228 | /// # Arguments 229 | /// * `network` - The network configuration to use for the retry-able operation. 230 | /// * `task` - The task to retry. 231 | pub async fn retry(network: NetworkConfig, mut task: F) -> Result> 232 | where 233 | F: FnMut(Client) -> T + Send, 234 | T: core::future::Future> + Send, 235 | T::Output: Send, 236 | E: Send, 237 | { 238 | if network.rpc_endpoints.is_empty() { 239 | return Err(RetryError::NoRpcEndpoints); 240 | } 241 | 242 | let mut last_error = None; 243 | for (index, endpoint) in network.rpc_endpoints.iter().enumerate() { 244 | let client = network 245 | .client(index) 246 | .map_err(|e| RetryError::InvalidApiKey(e))?; 247 | for retry in 0..endpoint.retries { 248 | let result = task(client.clone()).await; 249 | match result { 250 | RetryResponse::Ok(result) => return Ok(result), 251 | RetryResponse::Retry(error) => { 252 | last_error = Some(error); 253 | tokio::time::sleep(endpoint.get_sleep_duration(retry as usize)).await; 254 | } 255 | RetryResponse::Critical(result) => return Err(RetryError::Critical(result)), 256 | } 257 | } 258 | } 259 | Err(RetryError::RetriesExhausted(last_error.expect( 260 | "Logic error: last_error should be Some when all retries are exhausted", 261 | ))) 262 | } 263 | -------------------------------------------------------------------------------- /api/src/fastnear.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | 3 | use near_primitives::types::AccountId; 4 | use serde::de::DeserializeOwned; 5 | 6 | use crate::errors::FastNearError; 7 | 8 | #[derive(Debug, serde::Deserialize)] 9 | pub struct StakingPool { 10 | pool_id: near_primitives::types::AccountId, 11 | } 12 | 13 | #[derive(Debug, serde::Deserialize)] 14 | pub struct StakingResponse { 15 | pools: Vec, 16 | } 17 | 18 | pub struct FastNearBuilder { 19 | query: String, 20 | post_process: Box PostProcessed + Send + Sync>, 21 | _response: std::marker::PhantomData, 22 | } 23 | 24 | impl FastNearBuilder { 25 | pub fn new(query: String) -> Self { 26 | Self { 27 | query, 28 | post_process: Box::new(|response| response), 29 | _response: Default::default(), 30 | } 31 | } 32 | } 33 | 34 | impl FastNearBuilder 35 | where 36 | T: DeserializeOwned + Send + Sync, 37 | { 38 | pub fn map(query: String, func: F) -> Self 39 | where 40 | F: Fn(T) -> PostProcessed + Send + Sync + 'static, 41 | { 42 | Self { 43 | query, 44 | post_process: Box::new(func), 45 | _response: Default::default(), 46 | } 47 | } 48 | 49 | pub async fn fetch_from_url(self, url: url::Url) -> Result { 50 | let request = reqwest::get(url.join(&self.query)?).await?; 51 | Ok((self.post_process)(request.json().await?)) 52 | } 53 | 54 | pub async fn fetch_from_mainnet(self) -> Result { 55 | match crate::config::NetworkConfig::mainnet().fastnear_url { 56 | Some(url) => self.fetch_from_url(url).await, 57 | None => Err(FastNearError::FastNearUrlIsNotDefined), 58 | } 59 | } 60 | } 61 | 62 | #[derive(Clone, Debug)] 63 | pub struct FastNear {} 64 | 65 | impl FastNear { 66 | pub async fn pools_delegated_by( 67 | &self, 68 | account_id: &AccountId, 69 | ) -> Result>, FastNearError> { 70 | let query_builder = FastNearBuilder::map( 71 | format!("v1/account/{}/staking", account_id), 72 | |response: StakingResponse| { 73 | response 74 | .pools 75 | .into_iter() 76 | .map(|pool| pool.pool_id) 77 | .collect() 78 | }, 79 | ); 80 | 81 | Ok(query_builder) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /api/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A Rust library for interacting with the NEAR Protocol blockchain 2 | //! 3 | //! This crate provides a high-level API for interacting with NEAR Protocol, including: 4 | //! - [Account management and creation](Account) 5 | //! - [Contract deployment and interaction with it](Contract) 6 | //! - [Token operations](Tokens) ([`NEAR`](https://docs.near.org/concepts/basics/tokens), [`FT`](https://docs.near.org/build/primitives/ft), [`NFT`](https://docs.near.org/build/primitives/nft)) 7 | //! - [Storage management](StorageDeposit) 8 | //! - [Staking operations](Staking) 9 | //! - [Custom transaction building and signing](Transaction) 10 | //! - [Querying the chain data](Chain) 11 | //! - [Several ways to sign the transaction](signer) 12 | //! - Account nonce caching and access-key pooling mechanisms to speed up the transaction processing. 13 | //! - Support for backup RPC endpoints 14 | //! 15 | //! # Example 16 | //! In this example, we use Bob account with a predefined seed phrase to create Alice account and pre-fund it with 1 `NEAR`. 17 | //! ```rust,no_run 18 | //! use near_api::{*, signer::generate_secret_key}; 19 | //! use std::str::FromStr; 20 | //! 21 | //! # async fn example() -> Result<(), Box> { 22 | //! // Initialize network configuration 23 | //! let bob = AccountId::from_str("bob.testnet")?; 24 | //! let bob_seed_phrase = "lucky barrel fall come bottom can rib join rough around subway cloth "; 25 | //! 26 | //! // Fetch NEAR balance 27 | //! let _bob_balance = Tokens::account(bob.clone()) 28 | //! .near_balance() 29 | //! .fetch_from_testnet() 30 | //! .await?; 31 | //! 32 | //! // Create an account instance 33 | //! let signer = Signer::new(Signer::from_seed_phrase(bob_seed_phrase, None)?)?; 34 | //! let alice_secret_key = generate_secret_key()?; 35 | //! Account::create_account(AccountId::from_str("alice.testnet")?) 36 | //! .fund_myself(bob.clone(), NearToken::from_near(1)) 37 | //! .public_key(alice_secret_key.public_key())? 38 | //! .with_signer(signer) 39 | //! .send_to_testnet() 40 | //! .await?; 41 | //! # Ok(()) 42 | //! # } 43 | //! ``` 44 | //! 45 | //! # Features 46 | //! - `ledger`: Enables hardware wallet support 47 | //! - `keystore`: Enables system keychain integration 48 | //! - `workspaces`: Enables integration with near-workspaces for testing 49 | 50 | mod account; 51 | mod chain; 52 | mod config; 53 | mod contract; 54 | mod stake; 55 | mod storage; 56 | mod tokens; 57 | mod transactions; 58 | 59 | // TODO: to be honest, there is almost nothing in this file 60 | // we should maybe integrate with them more tightly 61 | // for now, i comment it out 62 | // mod fastnear; 63 | 64 | mod common; 65 | 66 | pub use near_api_types as types; 67 | pub mod errors; 68 | pub mod signer; 69 | 70 | pub use crate::{ 71 | account::Account, 72 | chain::Chain, 73 | config::{NetworkConfig, RPCEndpoint, RetryMethod}, 74 | contract::Contract, 75 | signer::{Signer, SignerTrait}, 76 | stake::{Delegation, Staking}, 77 | storage::StorageDeposit, 78 | tokens::Tokens, 79 | transactions::Transaction, 80 | types::{ 81 | AccountId, CryptoHash, Data, EpochReference, NearGas, NearToken, PublicKey, Reference, 82 | SecretKey, 83 | tokens::{FTBalance, USDT_BALANCE, W_NEAR_BALANCE}, 84 | }, 85 | }; 86 | 87 | pub mod advanced { 88 | pub use crate::common::query::*; 89 | pub use crate::common::send::*; 90 | } 91 | -------------------------------------------------------------------------------- /api/src/signer/keystore.rs: -------------------------------------------------------------------------------- 1 | use futures::future::join_all; 2 | use near_api_types::{AccessKeyPermission, AccountId, PublicKey, SecretKey}; 3 | use tracing::{debug, info, instrument, trace, warn}; 4 | 5 | use crate::{ 6 | config::NetworkConfig, 7 | errors::{KeyStoreError, SignerError}, 8 | }; 9 | 10 | use super::{AccountKeyPair, SignerTrait}; 11 | 12 | const KEYSTORE_SIGNER_TARGET: &str = "near_api::signer::keystore"; 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct KeystoreSigner { 16 | potential_pubkeys: Vec, 17 | } 18 | 19 | #[async_trait::async_trait] 20 | impl SignerTrait for KeystoreSigner { 21 | #[instrument(skip(self))] 22 | async fn get_secret_key( 23 | &self, 24 | signer_id: &AccountId, 25 | public_key: &PublicKey, 26 | ) -> Result { 27 | debug!(target: KEYSTORE_SIGNER_TARGET, "Searching for matching public key"); 28 | self.potential_pubkeys 29 | .iter() 30 | .find(|key| *key == public_key) 31 | .ok_or(SignerError::PublicKeyIsNotAvailable)?; 32 | 33 | info!(target: KEYSTORE_SIGNER_TARGET, "Retrieving secret key"); 34 | // TODO: fix this. Well the search is a bit suboptimal, but it's not a big deal for now 35 | let secret = if let Ok(secret) = 36 | Self::get_secret_key(signer_id, public_key.clone(), "mainnet").await 37 | { 38 | secret 39 | } else { 40 | Self::get_secret_key(signer_id, public_key.clone(), "testnet") 41 | .await 42 | .map_err(|_| SignerError::SecretKeyIsNotAvailable)? 43 | }; 44 | 45 | info!(target: KEYSTORE_SIGNER_TARGET, "Secret key prepared successfully"); 46 | Ok(secret.private_key) 47 | } 48 | 49 | #[instrument(skip(self))] 50 | fn get_public_key(&self) -> Result { 51 | debug!(target: KEYSTORE_SIGNER_TARGET, "Retrieving first public key"); 52 | self.potential_pubkeys 53 | .first() 54 | .cloned() 55 | .ok_or(SignerError::PublicKeyIsNotAvailable) 56 | } 57 | } 58 | 59 | impl KeystoreSigner { 60 | pub fn new_with_pubkey(pub_key: PublicKey) -> Self { 61 | debug!(target: KEYSTORE_SIGNER_TARGET, "Creating new KeystoreSigner with public key"); 62 | Self { 63 | potential_pubkeys: vec![pub_key], 64 | } 65 | } 66 | 67 | #[instrument(skip(network), fields(account_id = %account_id, network_name = %network.network_name))] 68 | pub async fn search_for_keys( 69 | account_id: AccountId, 70 | network: &NetworkConfig, 71 | ) -> Result { 72 | info!(target: KEYSTORE_SIGNER_TARGET, "Searching for keys for account"); 73 | let account_keys = crate::account::Account(account_id.clone()) 74 | .list_keys() 75 | .fetch_from(network) 76 | .await 77 | .map_err(KeyStoreError::QueryError)?; 78 | 79 | debug!(target: KEYSTORE_SIGNER_TARGET, "Filtering and collecting potential public keys"); 80 | let potential_pubkeys = account_keys 81 | .data 82 | .iter() 83 | // TODO: support functional access keys 84 | .filter(|(_, access_key)| { 85 | matches!(access_key.permission, AccessKeyPermission::FullAccess) 86 | }) 87 | .map(|(public_key, _)| public_key.clone()) 88 | .map(|key| Self::get_secret_key(&account_id, key, &network.network_name)); 89 | let potential_pubkeys: Vec = join_all(potential_pubkeys) 90 | .await 91 | .into_iter() 92 | .flat_map(|result| result.map(|keypair| keypair.public_key).ok()) 93 | .collect(); 94 | 95 | info!(target: KEYSTORE_SIGNER_TARGET, "KeystoreSigner created with {} potential public keys", potential_pubkeys.len()); 96 | Ok(Self { potential_pubkeys }) 97 | } 98 | 99 | #[instrument(skip(public_key), fields(account_id = %account_id, network_name = %network_name))] 100 | async fn get_secret_key( 101 | account_id: &AccountId, 102 | public_key: PublicKey, 103 | network_name: &str, 104 | ) -> Result { 105 | trace!(target: KEYSTORE_SIGNER_TARGET, "Retrieving secret key from keyring"); 106 | let service_name = 107 | std::borrow::Cow::Owned(format!("near-{}-{}", network_name, account_id.as_str())); 108 | let user = format!("{account_id}:{public_key}"); 109 | 110 | // This can be a blocking operation (for example, if the keyring is locked in the OS and user needs to unlock it), 111 | // so we need to spawn a new task to get the password 112 | let password = tokio::task::spawn_blocking(move || { 113 | let password = keyring::Entry::new(&service_name, &user)?.get_password()?; 114 | 115 | Ok::<_, KeyStoreError>(password) 116 | }) 117 | .await 118 | .unwrap_or_else(|tokio_join_error| Err(KeyStoreError::from(tokio_join_error)))?; 119 | 120 | debug!(target: KEYSTORE_SIGNER_TARGET, "Deserializing account key pair"); 121 | Ok(serde_json::from_str(&password)?) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /api/src/signer/ledger.rs: -------------------------------------------------------------------------------- 1 | use near_api_types::{ 2 | AccountId, BlockHeight, CryptoHash, Nonce, PublicKey, SecretKey, Signature, 3 | crypto::KeyType, 4 | transaction::{ 5 | PrepopulateTransaction, SignedTransaction, Transaction, TransactionV0, 6 | delegate_action::{DelegateAction, NonDelegateAction, SignedDelegateAction}, 7 | }, 8 | }; 9 | use slipped10::BIP32Path; 10 | use tokio::sync::OnceCell; 11 | use tracing::{debug, info, instrument, warn}; 12 | 13 | use crate::errors::{LedgerError, MetaSignError, SignerError}; 14 | 15 | use super::{NEP413Payload, SignerTrait}; 16 | 17 | const LEDGER_SIGNER_TARGET: &str = "near_api::signer::ledger"; 18 | 19 | #[derive(Debug, Clone)] 20 | pub struct LedgerSigner { 21 | hd_path: BIP32Path, 22 | public_key: OnceCell, 23 | } 24 | 25 | impl LedgerSigner { 26 | pub const fn new(hd_path: BIP32Path) -> Self { 27 | Self { 28 | hd_path, 29 | public_key: OnceCell::const_new(), 30 | } 31 | } 32 | } 33 | 34 | #[async_trait::async_trait] 35 | impl SignerTrait for LedgerSigner { 36 | #[instrument(skip(self, tr), fields(signer_id = %tr.signer_id, receiver_id = %tr.receiver_id))] 37 | async fn sign( 38 | &self, 39 | tr: PrepopulateTransaction, 40 | public_key: PublicKey, 41 | nonce: Nonce, 42 | block_hash: CryptoHash, 43 | ) -> Result { 44 | debug!(target: LEDGER_SIGNER_TARGET, "Preparing unsigned transaction"); 45 | let unsigned_tx = Transaction::V0(TransactionV0 { 46 | signer_id: tr.signer_id.clone(), 47 | public_key, 48 | receiver_id: tr.receiver_id, 49 | nonce, 50 | block_hash, 51 | actions: tr.actions, 52 | }); 53 | let unsigned_tx_bytes = borsh::to_vec(&unsigned_tx).map_err(LedgerError::from)?; 54 | let hd_path = self.hd_path.clone(); 55 | 56 | info!(target: LEDGER_SIGNER_TARGET, "Signing transaction with Ledger"); 57 | let signature = tokio::task::spawn_blocking(move || { 58 | let unsigned_tx_bytes = unsigned_tx_bytes; 59 | let signature = near_ledger::sign_transaction(&unsigned_tx_bytes, hd_path) 60 | .map_err(LedgerError::from)?; 61 | 62 | Ok::<_, LedgerError>(signature) 63 | }) 64 | .await 65 | .map_err(LedgerError::from)?; 66 | 67 | let signature = signature?; 68 | 69 | debug!(target: LEDGER_SIGNER_TARGET, "Creating Signature object"); 70 | let signature = Signature::from_parts(KeyType::ED25519, signature.as_ref()) 71 | .map_err(|e| LedgerError::SignatureDeserializationError(e.to_string()))?; 72 | 73 | info!(target: LEDGER_SIGNER_TARGET, "Transaction signed successfully"); 74 | Ok(SignedTransaction::new(signature, unsigned_tx)) 75 | } 76 | 77 | #[instrument(skip(self, tr), fields(signer_id = %tr.signer_id, receiver_id = %tr.receiver_id))] 78 | async fn sign_meta( 79 | &self, 80 | tr: PrepopulateTransaction, 81 | public_key: PublicKey, 82 | nonce: Nonce, 83 | _block_hash: CryptoHash, 84 | max_block_height: BlockHeight, 85 | ) -> Result { 86 | debug!(target: LEDGER_SIGNER_TARGET, "Preparing delegate action"); 87 | let actions = tr 88 | .actions 89 | .into_iter() 90 | .map(NonDelegateAction::try_from) 91 | .collect::>() 92 | .map_err(|_| MetaSignError::DelegateActionIsNotSupported)?; 93 | let delegate_action = DelegateAction { 94 | sender_id: tr.signer_id, 95 | receiver_id: tr.receiver_id, 96 | actions, 97 | nonce, 98 | max_block_height, 99 | public_key, 100 | }; 101 | 102 | let delegate_action_bytes = borsh::to_vec(&delegate_action) 103 | .map_err(LedgerError::from) 104 | .map_err(SignerError::from)?; 105 | let hd_path = self.hd_path.clone(); 106 | 107 | info!(target: LEDGER_SIGNER_TARGET, "Signing delegate action with Ledger"); 108 | let signature = tokio::task::spawn_blocking(move || { 109 | let delegate_action_bytes = delegate_action_bytes; 110 | let signature = 111 | near_ledger::sign_message_nep366_delegate_action(&delegate_action_bytes, hd_path) 112 | .map_err(LedgerError::from)?; 113 | 114 | Ok::<_, LedgerError>(signature) 115 | }) 116 | .await 117 | .map_err(LedgerError::from) 118 | .map_err(SignerError::from)?; 119 | 120 | let signature = signature.map_err(SignerError::from)?; 121 | 122 | debug!(target: LEDGER_SIGNER_TARGET, "Creating Signature object for delegate action"); 123 | let signature = 124 | Signature::from_parts(KeyType::ED25519, signature.as_ref()).map_err(|e| { 125 | SignerError::LedgerError(LedgerError::SignatureDeserializationError(e.to_string())) 126 | })?; 127 | 128 | info!(target: LEDGER_SIGNER_TARGET, "Delegate action signed successfully"); 129 | Ok(SignedDelegateAction { 130 | delegate_action, 131 | signature, 132 | }) 133 | } 134 | 135 | #[instrument(skip(self), fields(signer_id = %_signer_id, receiver_id = %payload.recipient, message = %payload.message))] 136 | async fn sign_message_nep413( 137 | &self, 138 | _signer_id: AccountId, 139 | _public_key: PublicKey, 140 | payload: NEP413Payload, 141 | ) -> Result { 142 | info!(target: LEDGER_SIGNER_TARGET, "Signing NEP413 message with Ledger"); 143 | let hd_path = self.hd_path.clone(); 144 | let payload = payload.into(); 145 | 146 | let signature: Vec = tokio::task::spawn_blocking(move || { 147 | let signature = 148 | near_ledger::sign_message_nep413(&payload, hd_path).map_err(LedgerError::from)?; 149 | 150 | Ok::<_, LedgerError>(signature) 151 | }) 152 | .await 153 | .unwrap_or_else(|tokio_join_error| Err(LedgerError::from(tokio_join_error)))?; 154 | 155 | debug!(target: LEDGER_SIGNER_TARGET, "Creating Signature object for NEP413"); 156 | let signature = 157 | Signature::from_parts(KeyType::ED25519, signature.as_ref()).map_err(|e| { 158 | SignerError::LedgerError(LedgerError::SignatureDeserializationError(e.to_string())) 159 | })?; 160 | 161 | Ok(signature) 162 | } 163 | 164 | async fn get_secret_key( 165 | &self, 166 | _signer_id: &AccountId, 167 | _public_key: &PublicKey, 168 | ) -> Result { 169 | warn!(target: LEDGER_SIGNER_TARGET, "Attempted to access secret key, which is not available for Ledger signer"); 170 | Err(SignerError::SecretKeyIsNotAvailable) 171 | } 172 | 173 | #[instrument(skip(self))] 174 | fn get_public_key(&self) -> Result { 175 | if let Some(public_key) = self.public_key.get() { 176 | Ok(public_key.clone()) 177 | } else { 178 | let public_key = near_ledger::get_wallet_id(self.hd_path.clone()) 179 | .map_err(|_| SignerError::PublicKeyIsNotAvailable)?; 180 | let public_key = PublicKey::ED25519( 181 | near_api_types::crypto::public_key::ED25519PublicKey(*public_key.as_bytes()), 182 | ); 183 | self.public_key 184 | .set(public_key.clone()) 185 | .map_err(LedgerError::from)?; 186 | Ok(public_key) 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /api/src/signer/secret_key.rs: -------------------------------------------------------------------------------- 1 | use tracing::{instrument, trace}; 2 | 3 | use near_api_types::{AccountId, PublicKey, SecretKey}; 4 | 5 | use crate::errors::SignerError; 6 | 7 | use super::SignerTrait; 8 | 9 | const SECRET_KEY_SIGNER_TARGET: &str = "near_api::signer::secret_key"; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct SecretKeySigner { 13 | secret_key: SecretKey, 14 | public_key: PublicKey, 15 | } 16 | 17 | #[async_trait::async_trait] 18 | impl SignerTrait for SecretKeySigner { 19 | #[instrument(skip(self))] 20 | async fn get_secret_key( 21 | &self, 22 | signer_id: &AccountId, 23 | public_key: &PublicKey, 24 | ) -> Result { 25 | trace!(target: SECRET_KEY_SIGNER_TARGET, "returning with secret key"); 26 | Ok(self.secret_key.clone()) 27 | } 28 | 29 | #[instrument(skip(self))] 30 | fn get_public_key(&self) -> Result { 31 | Ok(self.public_key.clone()) 32 | } 33 | } 34 | 35 | impl SecretKeySigner { 36 | pub fn new(secret_key: SecretKey) -> Self { 37 | let public_key = secret_key.public_key(); 38 | Self { 39 | secret_key, 40 | public_key, 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /api/src/storage.rs: -------------------------------------------------------------------------------- 1 | use near_api_types::{AccountId, Data, NearToken, StorageBalance, StorageBalanceInternal}; 2 | use serde_json::json; 3 | 4 | use crate::{ 5 | common::query::{CallResultHandler, PostprocessHandler, RequestBuilder}, 6 | contract::{Contract, ContractTransactBuilder}, 7 | errors::BuilderError, 8 | transactions::ConstructTransaction, 9 | }; 10 | 11 | ///A wrapper struct that simplifies interactions with the [Storage Management](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) standard 12 | /// 13 | /// Contracts on NEAR Protocol often implement a [NEP-145](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) for managing storage deposits, 14 | /// which are required for storing data on the blockchain. This struct provides convenient methods 15 | /// to interact with these storage-related functions on the contract. 16 | /// 17 | /// # Example 18 | /// ``` 19 | /// use near_api::*; 20 | /// 21 | /// # async fn example() -> Result<(), Box> { 22 | /// let storage = StorageDeposit::on_contract("contract.testnet".parse()?); 23 | /// 24 | /// // Check storage balance 25 | /// let balance = storage.view_account_storage("alice.testnet".parse()?)?.fetch_from_testnet().await?; 26 | /// println!("Storage balance: {:?}", balance); 27 | /// 28 | /// // Bob pays for Alice's storage on the contract contract.testnet 29 | /// let deposit_tx = storage.deposit("alice.testnet".parse()?, NearToken::from_near(1))? 30 | /// .with_signer("bob.testnet".parse()?, Signer::new(Signer::from_ledger())?) 31 | /// .send_to_testnet() 32 | /// .await 33 | /// .unwrap(); 34 | /// # Ok(()) 35 | /// # } 36 | /// ``` 37 | #[derive(Clone, Debug)] 38 | pub struct StorageDeposit(AccountId); 39 | 40 | impl StorageDeposit { 41 | pub const fn on_contract(contract_id: AccountId) -> Self { 42 | Self(contract_id) 43 | } 44 | 45 | /// Prepares a new contract query (`storage_balance_of`) for fetching the storage balance (Option<[StorageBalance]>) of the account on the contract. 46 | /// 47 | /// ## Example 48 | /// ```rust,no_run 49 | /// use near_api::*; 50 | /// 51 | /// # async fn example() -> Result<(), Box> { 52 | /// let balance = StorageDeposit::on_contract("contract.testnet".parse()?) 53 | /// .view_account_storage("alice.testnet".parse()?)? 54 | /// .fetch_from_testnet() 55 | /// .await?; 56 | /// println!("Storage balance: {:?}", balance); 57 | /// # Ok(()) 58 | /// # } 59 | /// ``` 60 | #[allow(clippy::type_complexity)] 61 | pub fn view_account_storage( 62 | &self, 63 | account_id: AccountId, 64 | ) -> Result< 65 | RequestBuilder< 66 | PostprocessHandler< 67 | Data>, 68 | CallResultHandler>, 69 | >, 70 | >, 71 | BuilderError, 72 | > { 73 | Ok(Contract(self.0.clone()) 74 | .call_function( 75 | "storage_balance_of", 76 | json!({ 77 | "account_id": account_id, 78 | }), 79 | )? 80 | .read_only() 81 | .map(|storage: Data>| { 82 | storage.map(|option_storage| { 83 | option_storage.map(|data| StorageBalance { 84 | available: data.available, 85 | total: data.total, 86 | locked: NearToken::from_yoctonear( 87 | data.total.as_yoctonear() - data.available.as_yoctonear(), 88 | ), 89 | }) 90 | }) 91 | })) 92 | } 93 | 94 | /// Prepares a new transaction contract call (`storage_deposit`) for depositing storage on the contract. 95 | /// 96 | /// ## Example 97 | /// ```rust,no_run 98 | /// use near_api::*; 99 | /// 100 | /// # async fn example() -> Result<(), Box> { 101 | /// let tx = StorageDeposit::on_contract("contract.testnet".parse()?) 102 | /// .deposit("alice.testnet".parse()?, NearToken::from_near(1))? 103 | /// .with_signer("bob.testnet".parse()?, Signer::new(Signer::from_ledger())?) 104 | /// .send_to_testnet() 105 | /// .await?; 106 | /// # Ok(()) 107 | /// # } 108 | /// ``` 109 | pub fn deposit( 110 | &self, 111 | receiver_account_id: AccountId, 112 | amount: NearToken, 113 | ) -> Result { 114 | Ok(Contract(self.0.clone()) 115 | .call_function( 116 | "storage_deposit", 117 | json!({ 118 | "account_id": receiver_account_id.to_string(), 119 | }), 120 | )? 121 | .transaction() 122 | .deposit(amount)) 123 | } 124 | 125 | /// Prepares a new transaction contract call (`storage_withdraw`) for withdrawing storage from the contract. 126 | /// 127 | /// ## Example 128 | /// ```rust,no_run 129 | /// use near_api::*; 130 | /// 131 | /// # async fn example() -> Result<(), Box> { 132 | /// let tx = StorageDeposit::on_contract("contract.testnet".parse()?) 133 | /// .withdraw("alice.testnet".parse()?, NearToken::from_near(1))? 134 | /// .with_signer(Signer::new(Signer::from_ledger())?) 135 | /// .send_to_testnet() 136 | /// .await?; 137 | /// # Ok(()) 138 | /// # } 139 | /// ``` 140 | pub fn withdraw( 141 | &self, 142 | account_id: AccountId, 143 | amount: NearToken, 144 | ) -> Result { 145 | Ok(Contract(self.0.clone()) 146 | .call_function( 147 | "storage_withdraw", 148 | json!({ 149 | "amount": amount.as_yoctonear() 150 | }), 151 | )? 152 | .transaction() 153 | .deposit(NearToken::from_yoctonear(1)) 154 | .with_signer_account(account_id)) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /api/src/transactions.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use near_api_types::{AccountId, Action, transaction::PrepopulateTransaction}; 4 | 5 | use crate::{ 6 | common::send::{ExecuteSignedTransaction, Transactionable}, 7 | config::NetworkConfig, 8 | errors::ValidationError, 9 | signer::Signer, 10 | }; 11 | 12 | #[derive(Clone, Debug)] 13 | pub struct TransactionWithSign { 14 | pub tx: T, 15 | } 16 | 17 | impl TransactionWithSign { 18 | pub fn with_signer(self, signer: Arc) -> ExecuteSignedTransaction { 19 | ExecuteSignedTransaction::new(self.tx, signer) 20 | } 21 | } 22 | 23 | #[derive(Clone, Debug)] 24 | pub struct SelfActionBuilder { 25 | pub actions: Vec, 26 | } 27 | 28 | impl Default for SelfActionBuilder { 29 | fn default() -> Self { 30 | Self::new() 31 | } 32 | } 33 | 34 | impl SelfActionBuilder { 35 | pub const fn new() -> Self { 36 | Self { 37 | actions: Vec::new(), 38 | } 39 | } 40 | 41 | /// Adds an action to the transaction. 42 | pub fn add_action(mut self, action: Action) -> Self { 43 | self.actions.push(action); 44 | self 45 | } 46 | 47 | /// Adds multiple actions to the transaction. 48 | pub fn add_actions(mut self, actions: Vec) -> Self { 49 | self.actions.extend(actions); 50 | self 51 | } 52 | 53 | /// Signs the transaction with the given account id and signer related to it. 54 | pub fn with_signer( 55 | self, 56 | signer_account_id: AccountId, 57 | signer: Arc, 58 | ) -> ExecuteSignedTransaction { 59 | ConstructTransaction::new(signer_account_id.clone(), signer_account_id) 60 | .add_actions(self.actions) 61 | .with_signer(signer) 62 | } 63 | } 64 | 65 | /// A builder for constructing transactions using Actions. 66 | #[derive(Debug, Clone)] 67 | pub struct ConstructTransaction { 68 | pub tr: PrepopulateTransaction, 69 | } 70 | 71 | impl ConstructTransaction { 72 | /// Pre-populates a transaction with the given signer and receiver IDs. 73 | pub const fn new(signer_id: AccountId, receiver_id: AccountId) -> Self { 74 | Self { 75 | tr: PrepopulateTransaction { 76 | signer_id, 77 | receiver_id, 78 | actions: Vec::new(), 79 | }, 80 | } 81 | } 82 | 83 | /// Adds an action to the transaction. 84 | pub fn add_action(mut self, action: Action) -> Self { 85 | self.tr.actions.push(action); 86 | self 87 | } 88 | 89 | /// Adds multiple actions to the transaction. 90 | pub fn add_actions(mut self, actions: Vec) -> Self { 91 | self.tr.actions.extend(actions); 92 | self 93 | } 94 | 95 | /// Signs the transaction with the given signer. 96 | pub fn with_signer(self, signer: Arc) -> ExecuteSignedTransaction { 97 | ExecuteSignedTransaction::new(self, signer) 98 | } 99 | } 100 | 101 | #[async_trait::async_trait] 102 | impl Transactionable for ConstructTransaction { 103 | fn prepopulated(&self) -> PrepopulateTransaction { 104 | PrepopulateTransaction { 105 | signer_id: self.tr.signer_id.clone(), 106 | receiver_id: self.tr.receiver_id.clone(), 107 | actions: self.tr.actions.clone(), 108 | } 109 | } 110 | 111 | async fn validate_with_network(&self, _: &NetworkConfig) -> Result<(), ValidationError> { 112 | Ok(()) 113 | } 114 | } 115 | 116 | /// Transaction related functionality. 117 | /// 118 | /// This struct provides ability to interact with transactions. 119 | #[derive(Clone, Debug)] 120 | pub struct Transaction; 121 | 122 | impl Transaction { 123 | /// Constructs a new transaction builder with the given signer and receiver IDs. 124 | /// This pattern is useful for batching actions into a single transaction. 125 | /// 126 | /// This is the low level interface for constructing transactions. 127 | /// It is designed to be used in scenarios where more control over the transaction process is required. 128 | /// 129 | /// # Example 130 | /// 131 | /// This example constructs a transaction with a two transfer actions. 132 | /// 133 | /// ```rust,no_run 134 | /// use near_api::{*, types::{transaction::actions::{Action, TransferAction}, json::U128}}; 135 | /// 136 | /// # async fn example() -> Result<(), Box> { 137 | /// let signer = Signer::new(Signer::from_ledger())?; 138 | /// 139 | /// let transaction_result = Transaction::construct( 140 | /// "sender.near".parse()?, 141 | /// "receiver.near".parse()? 142 | /// ) 143 | /// .add_action(Action::Transfer( 144 | /// TransferAction { 145 | /// deposit: NearToken::from_near(1), 146 | /// }, 147 | /// )) 148 | /// .add_action(Action::Transfer( 149 | /// TransferAction { 150 | /// deposit: NearToken::from_near(1), 151 | /// }, 152 | /// )) 153 | /// .with_signer(signer) 154 | /// .send_to_mainnet() 155 | /// .await?; 156 | /// # Ok(()) 157 | /// # } 158 | /// ``` 159 | pub const fn construct(signer_id: AccountId, receiver_id: AccountId) -> ConstructTransaction { 160 | ConstructTransaction::new(signer_id, receiver_id) 161 | } 162 | 163 | /// Signs a transaction with the given signer. 164 | /// 165 | /// This provides ability to sign custom constructed pre-populated transactions. 166 | /// 167 | /// # Examples 168 | /// 169 | /// ```rust,no_run 170 | /// use near_api::*; 171 | /// 172 | /// # async fn example() -> Result<(), Box> { 173 | /// let signer = Signer::new(Signer::from_ledger())?; 174 | /// # let unsigned_tx = todo!(); 175 | /// 176 | /// let transaction_result = Transaction::use_transaction( 177 | /// unsigned_tx, 178 | /// signer 179 | /// ) 180 | /// .send_to_mainnet() 181 | /// .await?; 182 | /// # Ok(()) 183 | /// # } 184 | /// ``` 185 | pub fn use_transaction( 186 | unsigned_tx: PrepopulateTransaction, 187 | signer: Arc, 188 | ) -> ExecuteSignedTransaction { 189 | ConstructTransaction::new(unsigned_tx.signer_id, unsigned_tx.receiver_id) 190 | .add_actions(unsigned_tx.actions) 191 | .with_signer(signer) 192 | } 193 | 194 | // TODO: fetch transaction status 195 | // TODO: fetch transaction receipt 196 | // TODO: fetch transaction proof 197 | } 198 | -------------------------------------------------------------------------------- /api/tests/account.rs: -------------------------------------------------------------------------------- 1 | use near_api::{ 2 | Account, NetworkConfig, Signer, Tokens, 3 | signer::generate_secret_key, 4 | types::{AccessKeyPermission, AccountId, NearToken, TxExecutionStatus}, 5 | }; 6 | use near_sandbox::{ 7 | GenesisAccount, SandboxConfig, 8 | config::{DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY}, 9 | }; 10 | 11 | #[tokio::test] 12 | async fn create_and_delete_account() { 13 | let network = near_sandbox::Sandbox::start_sandbox().await.unwrap(); 14 | 15 | let account_id: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 16 | let network: NetworkConfig = 17 | NetworkConfig::from_rpc_url("sandbox", network.rpc_addr.parse().unwrap()); 18 | let signer = Signer::new(Signer::from_secret_key( 19 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 20 | )) 21 | .unwrap(); 22 | 23 | let new_account: AccountId = format!("{}.{}", "bob", account_id).parse().unwrap(); 24 | let secret = generate_secret_key().unwrap(); 25 | let public_key = secret.public_key(); 26 | 27 | Account::create_account(new_account.clone()) 28 | .fund_myself(account_id.clone(), NearToken::from_near(1)) 29 | .public_key(public_key) 30 | .unwrap() 31 | .with_signer(signer.clone()) 32 | .send_to(&network) 33 | .await 34 | .unwrap() 35 | .assert_success(); 36 | 37 | let balance_before_del = Tokens::account(new_account.clone()) 38 | .near_balance() 39 | .fetch_from(&network) 40 | .await 41 | .unwrap(); 42 | 43 | assert_eq!(balance_before_del.total.as_near(), 1); 44 | 45 | Account(account_id.clone()) 46 | .delete_account_with_beneficiary(new_account.clone()) 47 | .with_signer(signer.clone()) 48 | .wait_until(TxExecutionStatus::Final) 49 | .send_to(&network) 50 | .await 51 | .unwrap() 52 | .assert_success(); 53 | 54 | Tokens::account(account_id.clone()) 55 | .near_balance() 56 | .fetch_from(&network) 57 | .await 58 | .expect_err("Shouldn't exist"); 59 | 60 | let balance_after_del = Tokens::account(new_account.clone()) 61 | .near_balance() 62 | .fetch_from(&network) 63 | .await 64 | .unwrap(); 65 | assert!(balance_after_del.total > balance_before_del.total); 66 | } 67 | 68 | #[tokio::test] 69 | async fn transfer_funds() { 70 | let alice: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 71 | let bob = GenesisAccount::generate_with_name("bob".parse().unwrap()); 72 | let sandbox = near_sandbox::Sandbox::start_sandbox_with_config(SandboxConfig { 73 | additional_accounts: vec![bob.clone()], 74 | ..Default::default() 75 | }) 76 | .await 77 | .unwrap(); 78 | let network: NetworkConfig = 79 | NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse().unwrap()); 80 | 81 | Tokens::account(alice.clone()) 82 | .send_to(bob.account_id.clone()) 83 | .near(NearToken::from_near(50)) 84 | .with_signer( 85 | Signer::new(Signer::from_secret_key( 86 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 87 | )) 88 | .unwrap(), 89 | ) 90 | .send_to(&network) 91 | .await 92 | .unwrap() 93 | .assert_success(); 94 | 95 | let alice_balance = Tokens::account(alice.clone()) 96 | .near_balance() 97 | .fetch_from(&network) 98 | .await 99 | .unwrap(); 100 | 101 | let bob_balance = Tokens::account(bob.account_id.clone()) 102 | .near_balance() 103 | .fetch_from(&network) 104 | .await 105 | .unwrap(); 106 | 107 | // it's actually 49.99 because of the fee 108 | assert_eq!(alice_balance.total.as_near(), 9949); 109 | assert_eq!(bob_balance.total.as_near(), 10050); 110 | } 111 | 112 | #[tokio::test] 113 | async fn access_key_management() { 114 | let network = near_sandbox::Sandbox::start_sandbox().await.unwrap(); 115 | let network: NetworkConfig = 116 | NetworkConfig::from_rpc_url("sandbox", network.rpc_addr.parse().unwrap()); 117 | let alice: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 118 | 119 | let alice_acc = Account(alice.clone()); 120 | let signer = Signer::new(Signer::from_secret_key( 121 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 122 | )) 123 | .unwrap(); 124 | 125 | let keys = alice_acc.list_keys().fetch_from(&network).await.unwrap(); 126 | assert_eq!(keys.data.len(), 1); 127 | 128 | let secret = generate_secret_key().unwrap(); 129 | let public_key = secret.public_key(); 130 | 131 | alice_acc 132 | .add_key(AccessKeyPermission::FullAccess, public_key.clone()) 133 | .with_signer(signer.clone()) 134 | .send_to(&network) 135 | .await 136 | .unwrap() 137 | .assert_success(); 138 | 139 | let keys = alice_acc.list_keys().fetch_from(&network).await.unwrap(); 140 | assert_eq!(keys.data.len(), 2); 141 | 142 | let new_key_info = alice_acc 143 | .access_key(public_key.clone()) 144 | .fetch_from(&network) 145 | .await 146 | .unwrap(); 147 | 148 | assert_eq!( 149 | new_key_info.data.permission, 150 | AccessKeyPermission::FullAccess 151 | ); 152 | 153 | alice_acc 154 | .delete_key(secret.public_key()) 155 | .with_signer(signer.clone()) 156 | .send_to(&network) 157 | .await 158 | .unwrap() 159 | .assert_success(); 160 | 161 | let keys = alice_acc.list_keys().fetch_from(&network).await.unwrap(); 162 | 163 | assert_eq!(keys.data.len(), 1); 164 | 165 | alice_acc 166 | .access_key(secret.public_key()) 167 | .fetch_from(&network) 168 | .await 169 | .expect_err("Shouldn't exist"); 170 | 171 | let signer = Signer::new(Signer::from_secret_key( 172 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 173 | )) 174 | .unwrap(); 175 | 176 | for _ in 0..10 { 177 | let secret = generate_secret_key().unwrap(); 178 | alice_acc 179 | .add_key(AccessKeyPermission::FullAccess, secret.public_key()) 180 | .with_signer(signer.clone()) 181 | .send_to(&network) 182 | .await 183 | .unwrap() 184 | .assert_success(); 185 | } 186 | 187 | let keys = alice_acc.list_keys().fetch_from(&network).await.unwrap(); 188 | 189 | assert_eq!(keys.data.len(), 11); 190 | 191 | alice_acc 192 | .delete_keys( 193 | keys.data 194 | .into_iter() 195 | .map(|(public_key, _)| public_key) 196 | .collect(), 197 | ) 198 | .with_signer(signer.clone()) 199 | .send_to(&network) 200 | .await 201 | .unwrap() 202 | .assert_success(); 203 | 204 | let keys = alice_acc.list_keys().fetch_from(&network).await.unwrap(); 205 | assert_eq!(keys.data.len(), 0); 206 | } 207 | -------------------------------------------------------------------------------- /api/tests/contract.rs: -------------------------------------------------------------------------------- 1 | use near_api::*; 2 | 3 | use near_api_types::{AccountId, Data}; 4 | use near_sandbox::config::{DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY}; 5 | use serde_json::json; 6 | 7 | #[tokio::test] 8 | async fn contract_without_init_call() { 9 | let network = near_sandbox::Sandbox::start_sandbox().await.unwrap(); 10 | let network = NetworkConfig::from_rpc_url("sandbox", network.rpc_addr.parse().unwrap()); 11 | let account: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 12 | let signer = Signer::new(Signer::from_secret_key( 13 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 14 | )) 15 | .unwrap(); 16 | 17 | Contract::deploy(account.clone()) 18 | .use_code(include_bytes!("../resources/counter.wasm").to_vec()) 19 | .without_init_call() 20 | .with_signer(signer.clone()) 21 | .send_to(&network) 22 | .await 23 | .unwrap() 24 | .assert_success(); 25 | 26 | let contract = Contract(account.clone()); 27 | 28 | assert!( 29 | !contract 30 | .wasm() 31 | .fetch_from(&network) 32 | .await 33 | .unwrap() 34 | .data 35 | .code_base64 36 | .is_empty() 37 | ); 38 | 39 | assert!( 40 | contract 41 | .contract_source_metadata() 42 | .fetch_from(&network) 43 | .await 44 | .unwrap() 45 | .data 46 | .version 47 | .is_some() 48 | ); 49 | 50 | let current_value: Data = contract 51 | .call_function("get_num", ()) 52 | .unwrap() 53 | .read_only() 54 | .fetch_from(&network) 55 | .await 56 | .unwrap(); 57 | assert_eq!(current_value.data, 0); 58 | 59 | contract 60 | .call_function("increment", ()) 61 | .unwrap() 62 | .transaction() 63 | .with_signer(account.clone(), signer.clone()) 64 | .send_to(&network) 65 | .await 66 | .unwrap() 67 | .assert_success(); 68 | 69 | let current_value: Data = contract 70 | .call_function("get_num", ()) 71 | .unwrap() 72 | .read_only() 73 | .fetch_from(&network) 74 | .await 75 | .unwrap(); 76 | 77 | assert_eq!(current_value.data, 1); 78 | } 79 | 80 | #[tokio::test] 81 | async fn contract_with_init_call() { 82 | let network = near_sandbox::Sandbox::start_sandbox().await.unwrap(); 83 | let network = NetworkConfig::from_rpc_url("sandbox", network.rpc_addr.parse().unwrap()); 84 | let account: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 85 | let signer = Signer::new(Signer::from_secret_key( 86 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 87 | )) 88 | .unwrap(); 89 | 90 | Contract::deploy(account.clone()) 91 | .use_code(include_bytes!("../resources/fungible_token.wasm").to_vec()) 92 | .with_init_call( 93 | "new_default_meta", 94 | json!({ 95 | "owner_id": account, 96 | "total_supply": "1000000000000000000000000000" 97 | }), 98 | ) 99 | .unwrap() 100 | .with_signer(signer.clone()) 101 | .send_to(&network) 102 | .await 103 | .unwrap() 104 | .assert_success(); 105 | 106 | let contract = Contract(account.clone()); 107 | 108 | assert!( 109 | !contract 110 | .wasm() 111 | .fetch_from(&network) 112 | .await 113 | .unwrap() 114 | .data 115 | .code_base64 116 | .is_empty() 117 | ); 118 | } 119 | -------------------------------------------------------------------------------- /api/tests/global_contracts.rs: -------------------------------------------------------------------------------- 1 | use near_api::*; 2 | 3 | use near_api_types::{AccountId, CryptoHash, Data}; 4 | use near_sandbox::{ 5 | GenesisAccount, SandboxConfig, 6 | config::{DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY}, 7 | }; 8 | 9 | #[tokio::test] 10 | async fn deploy_global_contract_as_account_id_and_use_it() { 11 | let global_contract = GenesisAccount::generate_with_name("global_contract".parse().unwrap()); 12 | let account_signer = Signer::new(Signer::from_secret_key( 13 | global_contract.private_key.parse().unwrap(), 14 | )) 15 | .unwrap(); 16 | 17 | let global_signer = Signer::new(Signer::from_secret_key( 18 | global_contract.private_key.parse().unwrap(), 19 | )) 20 | .unwrap(); 21 | 22 | let sandbox = near_sandbox::Sandbox::start_sandbox_with_config(SandboxConfig { 23 | additional_accounts: vec![global_contract.clone()], 24 | ..Default::default() 25 | }) 26 | .await 27 | .unwrap(); 28 | let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse().unwrap()); 29 | 30 | Contract::deploy_global_contract_code(include_bytes!("../resources/counter.wasm").to_vec()) 31 | .as_account_id(global_contract.account_id.clone()) 32 | .with_signer(global_signer.clone()) 33 | .send_to(&network) 34 | .await 35 | .unwrap() 36 | .assert_success(); 37 | 38 | Contract::deploy(global_contract.account_id.clone()) 39 | .use_global_account_id(global_contract.account_id.clone()) 40 | .without_init_call() 41 | .with_signer(account_signer.clone()) 42 | .send_to(&network) 43 | .await 44 | .unwrap() 45 | .assert_success(); 46 | 47 | let contract = Contract(global_contract.account_id.clone()); 48 | 49 | assert!( 50 | !contract 51 | .wasm() 52 | .fetch_from(&network) 53 | .await 54 | .unwrap() 55 | .data 56 | .code_base64 57 | .is_empty() 58 | ); 59 | 60 | assert!( 61 | contract 62 | .contract_source_metadata() 63 | .fetch_from(&network) 64 | .await 65 | .unwrap() 66 | .data 67 | .version 68 | .is_some() 69 | ); 70 | 71 | let current_value: Data = contract 72 | .call_function("get_num", ()) 73 | .unwrap() 74 | .read_only() 75 | .fetch_from(&network) 76 | .await 77 | .unwrap(); 78 | assert_eq!(current_value.data, 0); 79 | 80 | contract 81 | .call_function("increment", ()) 82 | .unwrap() 83 | .transaction() 84 | .with_signer(global_contract.account_id.clone(), account_signer.clone()) 85 | .send_to(&network) 86 | .await 87 | .unwrap() 88 | .assert_success(); 89 | 90 | let current_value: Data = contract 91 | .call_function("get_num", ()) 92 | .unwrap() 93 | .read_only() 94 | .fetch_from(&network) 95 | .await 96 | .unwrap(); 97 | 98 | assert_eq!(current_value.data, 1); 99 | } 100 | 101 | #[tokio::test] 102 | async fn deploy_global_contract_as_hash_and_use_it() { 103 | let global_contract = GenesisAccount::generate_with_name("global_contract".parse().unwrap()); 104 | let account_signer = Signer::new(Signer::from_secret_key( 105 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 106 | )) 107 | .unwrap(); 108 | let global_signer = Signer::new(Signer::from_secret_key( 109 | global_contract.private_key.parse().unwrap(), 110 | )) 111 | .unwrap(); 112 | let account_id: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 113 | 114 | let sandbox = near_sandbox::Sandbox::start_sandbox_with_config(SandboxConfig { 115 | additional_accounts: vec![global_contract.clone()], 116 | ..Default::default() 117 | }) 118 | .await 119 | .unwrap(); 120 | let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse().unwrap()); 121 | 122 | let code = include_bytes!("../resources/counter.wasm").to_vec(); 123 | let hash = CryptoHash::hash(&code); 124 | 125 | Contract::deploy_global_contract_code(code.clone()) 126 | .as_hash() 127 | .with_signer(global_contract.account_id.clone(), global_signer.clone()) 128 | .send_to(&network) 129 | .await 130 | .unwrap() 131 | .assert_success(); 132 | 133 | Contract::deploy(account_id.clone()) 134 | .use_global_hash(hash) 135 | .without_init_call() 136 | .with_signer(account_signer.clone()) 137 | .send_to(&network) 138 | .await 139 | .unwrap() 140 | .assert_success(); 141 | 142 | let contract = Contract(account_id.clone()); 143 | 144 | assert!( 145 | !contract 146 | .wasm() 147 | .fetch_from(&network) 148 | .await 149 | .unwrap() 150 | .data 151 | .code_base64 152 | .is_empty() 153 | ); 154 | 155 | assert!( 156 | contract 157 | .contract_source_metadata() 158 | .fetch_from(&network) 159 | .await 160 | .unwrap() 161 | .data 162 | .version 163 | .is_some() 164 | ); 165 | 166 | let current_value: Data = contract 167 | .call_function("get_num", ()) 168 | .unwrap() 169 | .read_only() 170 | .fetch_from(&network) 171 | .await 172 | .unwrap(); 173 | assert_eq!(current_value.data, 0); 174 | 175 | contract 176 | .call_function("increment", ()) 177 | .unwrap() 178 | .transaction() 179 | .with_signer(account_id.clone(), account_signer.clone()) 180 | .send_to(&network) 181 | .await 182 | .unwrap() 183 | .assert_success(); 184 | 185 | let current_value: Data = contract 186 | .call_function("get_num", ()) 187 | .unwrap() 188 | .read_only() 189 | .fetch_from(&network) 190 | .await 191 | .unwrap(); 192 | 193 | assert_eq!(current_value.data, 1); 194 | } 195 | -------------------------------------------------------------------------------- /api/tests/multiple_tx_at_same_time_from_same-_user.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Arc}; 2 | 3 | use futures::future::join_all; 4 | use near_api::*; 5 | use near_api_types::{AccessKeyPermission, AccountId, NearToken}; 6 | use near_sandbox::{ 7 | GenesisAccount, SandboxConfig, 8 | config::{ 9 | DEFAULT_GENESIS_ACCOUNT, DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY, 10 | DEFAULT_GENESIS_ACCOUNT_PUBLIC_KEY, 11 | }, 12 | }; 13 | use signer::generate_secret_key; 14 | 15 | #[tokio::test] 16 | async fn multiple_tx_at_same_time_from_same_key() { 17 | let tmp_account = GenesisAccount::generate_with_name("tmp_account".parse().unwrap()); 18 | let sandbox = near_sandbox::Sandbox::start_sandbox_with_config(SandboxConfig { 19 | additional_accounts: vec![tmp_account.clone()], 20 | ..Default::default() 21 | }) 22 | .await 23 | .unwrap(); 24 | let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse().unwrap()); 25 | let account: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 26 | let signer = Signer::new(Signer::from_secret_key( 27 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 28 | )) 29 | .unwrap(); 30 | 31 | let start_nonce = Account(account.clone()) 32 | .access_key(signer.get_public_key().await.unwrap()) 33 | .fetch_from(&network) 34 | .await 35 | .unwrap() 36 | .data 37 | .nonce; 38 | 39 | let tx = (0..100).map(|i| { 40 | Tokens::account(account.clone()) 41 | .send_to(tmp_account.account_id.clone()) 42 | .near(NearToken::from_millinear(i)) 43 | }); 44 | let txs = join_all(tx.map(|t| t.with_signer(Arc::clone(&signer)).send_to(&network))) 45 | .await 46 | .into_iter() 47 | .collect::, _>>() 48 | .unwrap(); 49 | 50 | assert_eq!(txs.len(), 100); 51 | 52 | let end_nonce = Account(account.clone()) 53 | .access_key(signer.get_public_key().await.unwrap()) 54 | .fetch_from(&network) 55 | .await 56 | .unwrap() 57 | .data 58 | .nonce; 59 | assert_eq!(end_nonce.0, start_nonce.0 + 100); 60 | } 61 | 62 | #[tokio::test] 63 | async fn multiple_tx_at_same_time_from_different_keys() { 64 | let tmp_account = GenesisAccount::generate_with_name("tmp_account".parse().unwrap()); 65 | let sandbox = near_sandbox::Sandbox::start_sandbox_with_config(SandboxConfig { 66 | additional_accounts: vec![tmp_account.clone()], 67 | ..Default::default() 68 | }) 69 | .await 70 | .unwrap(); 71 | let network = NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse().unwrap()); 72 | let account: AccountId = DEFAULT_GENESIS_ACCOUNT.into(); 73 | let signer = Signer::new(Signer::from_secret_key( 74 | DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY.parse().unwrap(), 75 | )) 76 | .unwrap(); 77 | 78 | let secret = generate_secret_key().unwrap(); 79 | Account(account.clone()) 80 | .add_key(AccessKeyPermission::FullAccess, secret.public_key()) 81 | .with_signer(signer.clone()) 82 | .send_to(&network) 83 | .await 84 | .unwrap() 85 | .assert_success(); 86 | 87 | signer 88 | .add_signer_to_pool(Signer::from_secret_key(secret.clone())) 89 | .await 90 | .unwrap(); 91 | 92 | let secret2 = generate_secret_key().unwrap(); 93 | Account(account.clone()) 94 | .add_key(AccessKeyPermission::FullAccess, secret2.public_key()) 95 | .with_signer(signer.clone()) 96 | .send_to(&network) 97 | .await 98 | .unwrap() 99 | .assert_success(); 100 | signer 101 | .add_signer_to_pool(Signer::from_secret_key(secret2.clone())) 102 | .await 103 | .unwrap(); 104 | 105 | let tx = (0..12).map(|i| { 106 | Tokens::account(account.clone()) 107 | .send_to(tmp_account.account_id.clone()) 108 | .near(NearToken::from_millinear(i)) 109 | }); 110 | let txs = join_all(tx.map(|t| t.with_signer(Arc::clone(&signer)).send_to(&network))) 111 | .await 112 | .into_iter() 113 | .map(|t| t.unwrap().assert_success()) 114 | .collect::>(); 115 | 116 | assert_eq!(txs.len(), 12); 117 | let mut hash_map = HashMap::new(); 118 | for tx in txs { 119 | let public_key = tx.transaction().public_key(); 120 | let count: &mut i32 = hash_map.entry(public_key.to_string()).or_insert(0); 121 | *count += 1; 122 | } 123 | 124 | assert_eq!(hash_map.len(), 3); 125 | assert_eq!(hash_map[DEFAULT_GENESIS_ACCOUNT_PUBLIC_KEY], 4); 126 | assert_eq!(hash_map[&secret2.public_key().to_string()], 4); 127 | assert_eq!(hash_map[&secret.public_key().to_string()], 4); 128 | } 129 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "ignorePaths": [], 4 | "dictionaryDefinitions": [], 5 | "dictionaries": [], 6 | "words": [ 7 | "libudev", 8 | "libsystemd", 9 | "clippy", 10 | "RUSTDOCFLAGS", 11 | "RUSTFLAGS", 12 | "Swatinem", 13 | "reqwest", 14 | "serde_json", 15 | "prepopulate", 16 | "transactionable", 17 | "linkdrop", 18 | "tgas", 19 | "yoctonear", 20 | "devhub", 21 | "presign", 22 | "borsh", 23 | "pubkeys", 24 | "keypair", 25 | "dalek", 26 | "delegators", 27 | "unstaked", 28 | "fastnear", 29 | "poolv1", 30 | "thiserror", 31 | "APDU", 32 | "multiquery", 33 | "unstake", 34 | "unstaking", 35 | "milli", 36 | "millinear", 37 | "serde", 38 | "rustdoc", 39 | "rustls", 40 | "zstd", 41 | "codeowners", 42 | "akorchyn", 43 | "frol", 44 | "ipfs", 45 | "nep", 46 | "cid", 47 | "wnear", 48 | "backlinks", 49 | "sourcescan", 50 | "msrv", 51 | "nearcore", 52 | "dtolnay", 53 | "Ieni", 54 | "repr", 55 | "secp", 56 | "Secp", 57 | "SECP", 58 | "chrono" 59 | ], 60 | "ignoreRegExpList": [ 61 | // Base58 encoded public key/secret key 62 | "\"ed25519:[-A-Za-z0-9+/]*={0,3}\"", 63 | "Email", 64 | ], 65 | "import": [] 66 | } 67 | -------------------------------------------------------------------------------- /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 | targets = ["wasm32-unknown-unknown"] 8 | -------------------------------------------------------------------------------- /types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "near-api-types" 3 | version = "0.6.1" 4 | authors = [ 5 | "akorchyn ", 6 | "frol ", 7 | "Near Inc ", 8 | ] 9 | license = "MIT OR Apache-2.0" 10 | edition = "2024" 11 | resolver = "2" 12 | repository = "https://github.com/near/near-api-rs" 13 | description = "Rust library to interact with NEAR Protocol via RPC API" 14 | 15 | 16 | [dependencies] 17 | borsh.workspace = true 18 | serde.workspace = true 19 | serde_json.workspace = true 20 | bs58.workspace = true 21 | thiserror.workspace = true 22 | base64.workspace = true 23 | sha2.workspace = true 24 | 25 | near-openapi-types.workspace = true 26 | near-account-id.workspace = true 27 | near-gas.workspace = true 28 | near-token.workspace = true 29 | near-abi.workspace = true 30 | primitive-types.workspace = true 31 | 32 | ed25519-dalek.workspace = true 33 | secp256k1.workspace = true 34 | 35 | [dev-dependencies] 36 | bolero.workspace = true 37 | near-primitives.workspace = true 38 | near-crypto.workspace = true 39 | -------------------------------------------------------------------------------- /types/src/account.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::{AccountId, CryptoHash, NearToken, StorageUsage, errors::DataConversionError}; 5 | 6 | #[derive( 7 | Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq, Default, 8 | )] 9 | pub enum ContractState { 10 | GlobalHash(CryptoHash), 11 | GlobalAccountId(AccountId), 12 | LocalHash(CryptoHash), 13 | #[default] 14 | None, 15 | } 16 | 17 | impl ContractState { 18 | pub const fn from_global_contract_hash(hash: CryptoHash) -> Self { 19 | Self::GlobalHash(hash) 20 | } 21 | 22 | pub const fn from_local_hash(hash: CryptoHash) -> Self { 23 | Self::LocalHash(hash) 24 | } 25 | } 26 | 27 | impl From for ContractState { 28 | fn from(value: AccountId) -> Self { 29 | Self::GlobalAccountId(value) 30 | } 31 | } 32 | 33 | #[derive(Debug, Clone, PartialEq, Eq)] 34 | pub struct Account { 35 | pub amount: NearToken, 36 | pub contract_state: ContractState, 37 | pub locked: NearToken, 38 | pub storage_usage: StorageUsage, 39 | } 40 | 41 | impl TryFrom for Account { 42 | type Error = DataConversionError; 43 | 44 | fn try_from(value: near_openapi_types::AccountView) -> Result { 45 | let near_openapi_types::AccountView { 46 | amount, 47 | code_hash, 48 | global_contract_account_id, 49 | global_contract_hash, 50 | locked, 51 | storage_paid_at: _, // Intentionally ignoring this field. See (https://github.com/near/nearcore/issues/2271) 52 | storage_usage, 53 | } = value; 54 | 55 | let code_hash = CryptoHash::try_from(code_hash)?; 56 | 57 | let contract_state = match (code_hash, global_contract_account_id, global_contract_hash) { 58 | (_, _, Some(hash)) => ContractState::from_global_contract_hash(hash.try_into()?), 59 | (_, Some(account_id), _) => account_id.into(), 60 | (hash, _, _) if hash == CryptoHash::default() => ContractState::None, 61 | (hash, _, _) => ContractState::from_local_hash(hash), 62 | }; 63 | 64 | Ok(Self { 65 | amount, 66 | contract_state, 67 | locked, 68 | storage_usage, 69 | }) 70 | } 71 | } 72 | 73 | impl serde::Serialize for Account { 74 | fn serialize(&self, serializer: S) -> Result 75 | where 76 | S: serde::Serializer, 77 | { 78 | let version = AccountVersion::V2; 79 | let code_hash = match self.contract_state { 80 | ContractState::LocalHash(hash) => hash, 81 | _ => CryptoHash::default(), 82 | }; 83 | let repr = SerdeAccount { 84 | amount: self.amount, 85 | locked: self.locked, 86 | code_hash, 87 | storage_usage: self.storage_usage, 88 | version, 89 | global_contract_hash: match &self.contract_state { 90 | ContractState::GlobalHash(hash) => Some(*hash), 91 | _ => None, 92 | }, 93 | global_contract_account_id: match &self.contract_state { 94 | ContractState::GlobalAccountId(account_id) => Some(account_id.clone()), 95 | _ => None, 96 | }, 97 | }; 98 | serde::Serialize::serialize(&repr, serializer) 99 | } 100 | } 101 | 102 | #[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] 103 | pub enum AccountVersion { 104 | V1, 105 | V2, 106 | } 107 | 108 | #[derive(Serialize, Deserialize, Debug, Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq)] 109 | pub struct SerdeAccount { 110 | pub amount: NearToken, 111 | pub locked: NearToken, 112 | pub code_hash: CryptoHash, 113 | pub storage_usage: u64, 114 | pub version: AccountVersion, 115 | pub global_contract_hash: Option, 116 | pub global_contract_account_id: Option, 117 | } 118 | -------------------------------------------------------------------------------- /types/src/contract.rs: -------------------------------------------------------------------------------- 1 | pub use build_info::BuildInfo; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | /// The struct provides information about deployed contract's source code and supported standards. 5 | /// 6 | /// Contract source metadata follows [**NEP-330 standard**](https://github.com/near/NEPs/blob/master/neps/nep-0330.md) for smart contracts 7 | /// 8 | /// See the documentation of [`Contract::contract_source_metadata`](near-api::Contract::contract_source_metadata) on how to query this for a contract via this crate 9 | // `rustdoc` clearly lacks functionality of automatic backlinks within a single crate 10 | #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] 11 | pub struct ContractSourceMetadata { 12 | /// Optional version identifier, typically a semantic version 13 | /// 14 | /// ## Examples: 15 | /// 16 | /// ```rust,no_run 17 | /// # let version: Option = 18 | /// // Semantic version 19 | /// Some("1.0.0".into()) 20 | /// # ; 21 | /// ``` 22 | /// ```rust,no_run 23 | /// # let version: Option = 24 | /// // Git commit 25 | /// Some("39f2d2646f2f60e18ab53337501370dc02a5661c".into()) 26 | /// # ; 27 | /// ``` 28 | pub version: Option, 29 | 30 | // cSpell::ignore bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq 31 | /// Optional URL to source code repository/tree 32 | /// 33 | /// ## Examples: 34 | /// 35 | /// ```rust,no_run 36 | /// # let link: Option = 37 | /// // GitHub URL 38 | /// Some("https://github.com/org/repo/tree/8d8a8a0fe86a1d8eb3bce45f04ab1a65fecf5a1b".into()) 39 | /// # ; 40 | /// ``` 41 | /// ```rust,no_run 42 | /// # let link: Option = 43 | /// // GitHub URL 44 | /// Some("https://github.com/near-examples/nft-tutorial".into()) 45 | /// # ; 46 | /// ``` 47 | /// ```rust,no_run 48 | /// # let link: Option = 49 | /// // IPFS CID 50 | /// Some("bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq".into()) 51 | /// # ; 52 | /// ``` 53 | pub link: Option, 54 | 55 | /// List of supported NEAR standards (NEPs) with their versions 56 | /// 57 | /// This field is an addition of **1.1.0** **NEP-330** revision 58 | /// 59 | /// ## Examples: 60 | /// 61 | /// This field will always include NEP-330 itself: 62 | /// ```rust,no_run 63 | /// # use near_api_types::contract::Standard; 64 | /// # let link: Vec = 65 | /// // this is always at least 1.1.0 66 | /// vec![Standard { standard: "nep330".into(), version: "1.1.0".into() }] 67 | /// # ; 68 | /// ``` 69 | /// ```rust,no_run 70 | /// # use near_api_types::contract::Standard; 71 | /// # let link: Vec = 72 | /// vec![Standard { standard: "nep330".into(), version: "1.2.0".into() }] 73 | /// # ; 74 | /// ``` 75 | // it's a guess it was added as 1.1.0 of nep330, [nep330 1.1.0 standard recording](https://www.youtube.com/watch?v=pBLN9UyE6AA) actually discusses nep351 76 | #[serde(default)] 77 | pub standards: Vec, 78 | 79 | /// Optional details that are required for formal contract WASM build reproducibility verification 80 | /// 81 | /// This field is an addition of **1.2.0** **NEP-330** revision 82 | pub build_info: Option, 83 | } 84 | 85 | /// NEAR Standard implementation descriptor following [NEP-330](https://github.com/near/NEPs/blob/master/neps/nep-0330.md) 86 | #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] 87 | pub struct Standard { 88 | /// Standard name in lowercase NEP format 89 | /// 90 | /// ## Examples: 91 | /// 92 | /// ```rust,no_run 93 | /// # let standard: String = 94 | /// // for fungible tokens 95 | /// "nep141".into() 96 | /// # ; 97 | /// ``` 98 | pub standard: String, 99 | 100 | /// Implemented standard version using semantic versioning 101 | /// 102 | /// ## Examples: 103 | /// 104 | /// ```rust,no_run 105 | /// # let version: String = 106 | /// // for initial release 107 | /// "1.0.0".into() 108 | /// # ; 109 | /// ``` 110 | pub version: String, 111 | } 112 | 113 | mod build_info { 114 | use serde::{Deserialize, Serialize}; 115 | 116 | #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] 117 | /// Defines all required details for formal WASM build reproducibility verification 118 | /// according to [**NEP-330 standard 1.3.0 revision**](https://github.com/near/NEPs/blob/master/neps/nep-0330.md) 119 | pub struct BuildInfo { 120 | /// Reference to a reproducible build environment docker image 121 | /// 122 | /// ## Examples: 123 | /// 124 | /// ```rust,no_run 125 | /// # let build_environment: String = 126 | /// "sourcescan/cargo-near:0.13.3-rust-1.84.0@sha256:722198ddb92d1b82cbfcd3a4a9f7fba6fd8715f4d0b5fb236d8725c4883f97de".into() 127 | /// # ; 128 | /// ``` 129 | pub build_environment: String, 130 | /// The exact command that was used to build the contract, with all the flags 131 | /// 132 | /// ## Examples: 133 | /// 134 | /// ```rust,no_run 135 | /// # let build_command: Vec = 136 | /// vec![ 137 | /// "cargo".into(), 138 | /// "near".into(), 139 | /// "build".into(), 140 | /// "non-reproducible-wasm".into(), 141 | /// "--locked".into() 142 | /// ] 143 | /// # ; 144 | /// ``` 145 | pub build_command: Vec, 146 | /// Relative path to contract crate within the source code 147 | /// 148 | /// ## Examples: 149 | /// 150 | /// ```rust,no_run 151 | /// # let contract_path: String = 152 | /// "near/omni-prover/wormhole-omni-prover-proxy".into() 153 | /// # ; 154 | /// ``` 155 | /// ```rust,no_run 156 | /// # let contract_path: String = 157 | /// // root of a repo 158 | /// "".into() 159 | /// # ; 160 | /// ``` 161 | pub contract_path: String, 162 | /// Reference to the source code snapshot that was used to build the contract 163 | /// 164 | /// ## Examples: 165 | /// 166 | /// ```rust,no_run 167 | /// # let source_code_snapshot: String = 168 | /// "git+https://github.com/org/repo?rev=8d8a8a0fe86a1d8eb3bce45f04ab1a65fecf5a1b".into() 169 | /// # ; 170 | /// ``` 171 | pub source_code_snapshot: String, 172 | /// A path within the build environment, where the result WASM binary has been put 173 | /// during build. 174 | /// This should be a subpath of `/home/near/code` 175 | /// 176 | /// This field is an addition of **1.3.0** **NEP-330** revision 177 | /// 178 | /// ## Examples: 179 | /// 180 | /// ```rust,no_run 181 | /// # let output_wasm_path: Option = 182 | /// Some("/home/near/code/target/near/simple_package.wasm".into()) 183 | /// # ; 184 | /// ``` 185 | pub output_wasm_path: Option, 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /types/src/crypto/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{Display, Formatter}, 3 | str::FromStr, 4 | }; 5 | 6 | use crate::errors::{DataConversionError, KeyTypeError}; 7 | 8 | pub mod public_key; 9 | pub mod secret_key; 10 | pub mod signature; 11 | 12 | pub const ED25519_PUBLIC_KEY_LENGTH: usize = 32; 13 | pub const SECP256K1_PUBLIC_KEY_LENGTH: usize = 64; 14 | pub const COMPONENT_SIZE: usize = 32; 15 | pub const SECP256K1_SIGNATURE_LENGTH: usize = 65; 16 | 17 | #[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] 18 | #[cfg_attr(test, derive(bolero::TypeGenerator))] 19 | pub enum KeyType { 20 | ED25519 = 0, 21 | SECP256K1 = 1, 22 | } 23 | 24 | impl Display for KeyType { 25 | fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { 26 | f.write_str(match self { 27 | Self::ED25519 => "ed25519", 28 | Self::SECP256K1 => "secp256k1", 29 | }) 30 | } 31 | } 32 | 33 | impl FromStr for KeyType { 34 | type Err = KeyTypeError; 35 | 36 | fn from_str(value: &str) -> Result { 37 | let lowercase_key_type = value.to_ascii_lowercase(); 38 | match lowercase_key_type.as_str() { 39 | "ed25519" => Ok(Self::ED25519), 40 | "secp256k1" => Ok(Self::SECP256K1), 41 | _ => Err(KeyTypeError::InvalidKeyFormat( 42 | lowercase_key_type.to_string(), 43 | )), 44 | } 45 | } 46 | } 47 | 48 | impl TryFrom for KeyType { 49 | type Error = KeyTypeError; 50 | 51 | fn try_from(value: u8) -> Result { 52 | match value { 53 | 0 => Ok(Self::ED25519), 54 | 1 => Ok(Self::SECP256K1), 55 | unknown_key_type => Err(KeyTypeError::InvalidKeyTypeByteIndex(unknown_key_type)), 56 | } 57 | } 58 | } 59 | 60 | fn split_key_type_data(value: &str) -> Result<(KeyType, &str), DataConversionError> { 61 | if let Some((prefix, key_data)) = value.split_once(':') { 62 | Ok((KeyType::from_str(prefix)?, key_data)) 63 | } else { 64 | // If there is no prefix then we Default to ED25519. 65 | Ok((KeyType::ED25519, value)) 66 | } 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | use super::{KeyType, public_key::PublicKey, secret_key::SecretKey, signature::Signature}; 72 | 73 | #[test] 74 | fn signature_verify_fuzzer() { 75 | bolero::check!().with_type().for_each( 76 | |(key_type, sign, data, public_key): &(KeyType, [u8; 65], Vec, PublicKey)| { 77 | let signature = match key_type { 78 | KeyType::ED25519 => { 79 | Signature::from_parts(KeyType::ED25519, &sign[..64]).unwrap() 80 | } 81 | KeyType::SECP256K1 => { 82 | Signature::from_parts(KeyType::SECP256K1, &sign[..65]).unwrap() 83 | } 84 | }; 85 | let _ = signature.verify(data, public_key); 86 | }, 87 | ); 88 | } 89 | 90 | #[test] 91 | fn regression_signature_verification_originally_failed() { 92 | let signature = Signature::from_parts(KeyType::SECP256K1, &[4; 65]).unwrap(); 93 | let _ = signature.verify(&[], &PublicKey::empty(KeyType::SECP256K1)); 94 | } 95 | 96 | #[test] 97 | fn test_invalid_data() { 98 | // cspell:disable-next-line 99 | let invalid = "\"secp256k1:2xVqteU8PWhadHTv99TGh3bSf\""; 100 | assert!(serde_json::from_str::(invalid).is_err()); 101 | assert!(serde_json::from_str::(invalid).is_err()); 102 | assert!(serde_json::from_str::(invalid).is_err()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /types/src/crypto/public_key.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{Debug, Display, Formatter}, 3 | hash::{Hash, Hasher}, 4 | io::{Error, ErrorKind, Read, Write}, 5 | str::FromStr, 6 | }; 7 | 8 | use borsh::{BorshDeserialize, BorshSerialize}; 9 | 10 | use crate::{ 11 | crypto::{KeyType, SECP256K1_PUBLIC_KEY_LENGTH, split_key_type_data}, 12 | errors::DataConversionError, 13 | }; 14 | 15 | /// Public key container supporting different curves. 16 | #[derive(Clone, PartialEq, PartialOrd, Ord, Eq)] 17 | #[cfg_attr(test, derive(bolero::TypeGenerator))] 18 | pub enum PublicKey { 19 | /// 256 bit elliptic curve based public-key. 20 | ED25519(ED25519PublicKey), 21 | /// 512 bit elliptic curve based public-key used in Bitcoin's public-key cryptography. 22 | SECP256K1(Secp256K1PublicKey), 23 | } 24 | 25 | impl PublicKey { 26 | // `is_empty` always returns false, so there is no point in adding it 27 | #[allow(clippy::len_without_is_empty)] 28 | pub const fn len(&self) -> usize { 29 | const ED25519_LEN: usize = ed25519_dalek::PUBLIC_KEY_LENGTH + 1; 30 | match self { 31 | Self::ED25519(_) => ED25519_LEN, 32 | Self::SECP256K1(_) => 65, 33 | } 34 | } 35 | 36 | pub const fn empty(key_type: KeyType) -> Self { 37 | match key_type { 38 | KeyType::ED25519 => { 39 | Self::ED25519(ED25519PublicKey([0u8; ed25519_dalek::PUBLIC_KEY_LENGTH])) 40 | } 41 | KeyType::SECP256K1 => Self::SECP256K1(Secp256K1PublicKey([0u8; 64])), 42 | } 43 | } 44 | 45 | pub const fn key_type(&self) -> KeyType { 46 | match self { 47 | Self::ED25519(_) => KeyType::ED25519, 48 | Self::SECP256K1(_) => KeyType::SECP256K1, 49 | } 50 | } 51 | 52 | pub const fn key_data(&self) -> &[u8] { 53 | match self { 54 | Self::ED25519(key) => &key.0, 55 | Self::SECP256K1(key) => &key.0, 56 | } 57 | } 58 | 59 | pub const fn unwrap_as_ed25519(&self) -> &ED25519PublicKey { 60 | match self { 61 | Self::ED25519(key) => key, 62 | Self::SECP256K1(_) => panic!(), 63 | } 64 | } 65 | 66 | pub const fn unwrap_as_secp256k1(&self) -> &Secp256K1PublicKey { 67 | match self { 68 | Self::SECP256K1(key) => key, 69 | Self::ED25519(_) => panic!(), 70 | } 71 | } 72 | } 73 | 74 | impl TryFrom for PublicKey { 75 | type Error = DataConversionError; 76 | fn try_from(val: near_openapi_types::PublicKey) -> Result { 77 | Self::from_str(&val.0) 78 | } 79 | } 80 | 81 | impl From for near_openapi_types::PublicKey { 82 | fn from(val: PublicKey) -> Self { 83 | Self(val.to_string()) 84 | } 85 | } 86 | 87 | // This `Hash` implementation is safe since it retains the property 88 | // `k1 == k2 ⇒ hash(k1) == hash(k2)`. 89 | impl Hash for PublicKey { 90 | fn hash(&self, state: &mut H) { 91 | match self { 92 | Self::ED25519(public_key) => { 93 | state.write_u8(0u8); 94 | state.write(&public_key.0); 95 | } 96 | Self::SECP256K1(public_key) => { 97 | state.write_u8(1u8); 98 | state.write(&public_key.0); 99 | } 100 | } 101 | } 102 | } 103 | 104 | impl Display for PublicKey { 105 | fn fmt(&self, fmt: &mut Formatter) -> std::fmt::Result { 106 | let (key_type, key_data) = match self { 107 | Self::ED25519(public_key) => (KeyType::ED25519, &public_key.0[..]), 108 | Self::SECP256K1(public_key) => (KeyType::SECP256K1, &public_key.0[..]), 109 | }; 110 | write!(fmt, "{}:{}", key_type, bs58::encode(key_data).into_string()) 111 | } 112 | } 113 | 114 | impl Debug for PublicKey { 115 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 116 | Display::fmt(self, f) 117 | } 118 | } 119 | 120 | impl BorshSerialize for PublicKey { 121 | fn serialize(&self, writer: &mut W) -> Result<(), Error> { 122 | match self { 123 | Self::ED25519(public_key) => { 124 | BorshSerialize::serialize(&0u8, writer)?; 125 | writer.write_all(&public_key.0)?; 126 | } 127 | Self::SECP256K1(public_key) => { 128 | BorshSerialize::serialize(&1u8, writer)?; 129 | writer.write_all(&public_key.0)?; 130 | } 131 | } 132 | Ok(()) 133 | } 134 | } 135 | 136 | impl BorshDeserialize for PublicKey { 137 | fn deserialize_reader(rd: &mut R) -> std::io::Result { 138 | let key_type = KeyType::try_from(u8::deserialize_reader(rd)?) 139 | .map_err(|err| Error::new(ErrorKind::InvalidData, err.to_string()))?; 140 | match key_type { 141 | KeyType::ED25519 => Ok(Self::ED25519(ED25519PublicKey( 142 | BorshDeserialize::deserialize_reader(rd)?, 143 | ))), 144 | KeyType::SECP256K1 => Ok(Self::SECP256K1(Secp256K1PublicKey( 145 | BorshDeserialize::deserialize_reader(rd)?, 146 | ))), 147 | } 148 | } 149 | } 150 | 151 | impl serde::Serialize for PublicKey { 152 | fn serialize( 153 | &self, 154 | serializer: S, 155 | ) -> Result<::Ok, ::Error> 156 | where 157 | S: serde::Serializer, 158 | { 159 | serializer.collect_str(self) 160 | } 161 | } 162 | 163 | impl<'de> serde::Deserialize<'de> for PublicKey { 164 | fn deserialize(deserializer: D) -> Result>::Error> 165 | where 166 | D: serde::Deserializer<'de>, 167 | { 168 | let s = ::deserialize(deserializer)?; 169 | s.parse() 170 | .map_err(|err: DataConversionError| serde::de::Error::custom(err.to_string())) 171 | } 172 | } 173 | 174 | impl FromStr for PublicKey { 175 | type Err = DataConversionError; 176 | 177 | fn from_str(value: &str) -> Result { 178 | let (key_type, key_data) = split_key_type_data(value)?; 179 | Ok(match key_type { 180 | KeyType::ED25519 => Self::ED25519(ED25519PublicKey( 181 | bs58::decode(key_data).into_vec()?.try_into()?, 182 | )), 183 | KeyType::SECP256K1 => Self::SECP256K1(Secp256K1PublicKey( 184 | bs58::decode(key_data).into_vec()?.try_into()?, 185 | )), 186 | }) 187 | } 188 | } 189 | 190 | #[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] 191 | #[cfg_attr(test, derive(bolero::TypeGenerator))] 192 | pub struct Secp256K1PublicKey(pub [u8; SECP256K1_PUBLIC_KEY_LENGTH]); 193 | 194 | impl TryFrom<&[u8]> for Secp256K1PublicKey { 195 | type Error = DataConversionError; 196 | 197 | fn try_from(data: &[u8]) -> Result { 198 | Ok(Self(data.try_into()?)) 199 | } 200 | } 201 | 202 | impl std::fmt::Debug for Secp256K1PublicKey { 203 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 204 | Display::fmt(&bs58::encode(&self.0).into_string(), f) 205 | } 206 | } 207 | 208 | #[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] 209 | #[cfg_attr(test, derive(bolero::TypeGenerator))] 210 | pub struct ED25519PublicKey(pub [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]); 211 | 212 | impl TryFrom<&[u8]> for ED25519PublicKey { 213 | type Error = DataConversionError; 214 | 215 | fn try_from(data: &[u8]) -> Result { 216 | Ok(Self(data.try_into()?)) 217 | } 218 | } 219 | 220 | impl std::fmt::Debug for ED25519PublicKey { 221 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 222 | Display::fmt(&bs58::encode(&self.0).into_string(), f) 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /types/src/crypto/secret_key.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, str::FromStr, sync::LazyLock}; 2 | 3 | use ed25519_dalek::ed25519::signature::SignerMut; 4 | 5 | use crate::{ 6 | PublicKey, Signature, 7 | crypto::{ 8 | KeyType, 9 | public_key::{ED25519PublicKey, Secp256K1PublicKey}, 10 | signature::Secp256K1Signature, 11 | split_key_type_data, 12 | }, 13 | errors::{DataConversionError, SecretKeyError}, 14 | }; 15 | 16 | pub static SECP256K1: LazyLock> = 17 | LazyLock::new(secp256k1::Secp256k1::new); 18 | 19 | /// Secret key container supporting different curves. 20 | #[derive(Clone, Eq, PartialEq, Debug)] 21 | pub enum SecretKey { 22 | ED25519(ED25519SecretKey), 23 | SECP256K1(secp256k1::SecretKey), 24 | } 25 | 26 | impl SecretKey { 27 | pub const fn key_type(&self) -> KeyType { 28 | match self { 29 | Self::ED25519(_) => KeyType::ED25519, 30 | Self::SECP256K1(_) => KeyType::SECP256K1, 31 | } 32 | } 33 | 34 | pub fn sign(&self, data: &[u8]) -> Signature { 35 | match &self { 36 | Self::ED25519(secret_key) => { 37 | let mut keypair = 38 | ed25519_dalek::SigningKey::from_keypair_bytes(&secret_key.0).unwrap(); 39 | Signature::ED25519(keypair.sign(data)) 40 | } 41 | 42 | Self::SECP256K1(secret_key) => { 43 | let signature = SECP256K1.sign_ecdsa_recoverable( 44 | &secp256k1::Message::from_slice(data).expect("32 bytes"), 45 | secret_key, 46 | ); 47 | let (rec_id, data) = signature.serialize_compact(); 48 | let mut buf = [0; 65]; 49 | buf[0..64].copy_from_slice(&data[0..64]); 50 | buf[64] = rec_id.to_i32() as u8; 51 | Signature::SECP256K1(Secp256K1Signature(buf)) 52 | } 53 | } 54 | } 55 | 56 | pub fn public_key(&self) -> PublicKey { 57 | match &self { 58 | Self::ED25519(secret_key) => PublicKey::ED25519(ED25519PublicKey( 59 | secret_key.0[ed25519_dalek::SECRET_KEY_LENGTH..] 60 | .try_into() 61 | .unwrap(), 62 | )), 63 | Self::SECP256K1(secret_key) => { 64 | let pk = secp256k1::PublicKey::from_secret_key(&SECP256K1, secret_key); 65 | let serialized = pk.serialize_uncompressed(); 66 | let mut public_key = Secp256K1PublicKey([0; 64]); 67 | public_key.0.copy_from_slice(&serialized[1..65]); 68 | PublicKey::SECP256K1(public_key) 69 | } 70 | } 71 | } 72 | 73 | pub fn unwrap_as_ed25519(&self) -> &ED25519SecretKey { 74 | match self { 75 | Self::ED25519(key) => key, 76 | Self::SECP256K1(_) => panic!("Secret key is not an ED25519 secret key"), 77 | } 78 | } 79 | } 80 | 81 | impl std::fmt::Display for SecretKey { 82 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 83 | let (key_type, key_data) = match self { 84 | Self::ED25519(secret_key) => (KeyType::ED25519, &secret_key.0[..]), 85 | Self::SECP256K1(secret_key) => (KeyType::SECP256K1, &secret_key[..]), 86 | }; 87 | write!(f, "{}:{}", key_type, bs58::encode(key_data).into_string()) 88 | } 89 | } 90 | 91 | impl FromStr for SecretKey { 92 | type Err = SecretKeyError; 93 | 94 | fn from_str(s: &str) -> Result { 95 | let (key_type, key_data) = split_key_type_data(s)?; 96 | Ok(match key_type { 97 | KeyType::ED25519 => Self::ED25519(ED25519SecretKey( 98 | bs58::decode(key_data) 99 | .into_vec() 100 | .map_err(DataConversionError::from)? 101 | .try_into()?, 102 | )), 103 | KeyType::SECP256K1 => { 104 | let data = bs58::decode(key_data) 105 | .into_vec() 106 | .map_err(DataConversionError::from)?; 107 | let sk = secp256k1::SecretKey::from_slice(&data)?; 108 | Self::SECP256K1(sk) 109 | } 110 | }) 111 | } 112 | } 113 | 114 | impl serde::Serialize for SecretKey { 115 | fn serialize( 116 | &self, 117 | serializer: S, 118 | ) -> Result<::Ok, ::Error> 119 | where 120 | S: serde::Serializer, 121 | { 122 | serializer.collect_str(self) 123 | } 124 | } 125 | 126 | impl<'de> serde::Deserialize<'de> for SecretKey { 127 | fn deserialize(deserializer: D) -> Result>::Error> 128 | where 129 | D: serde::Deserializer<'de>, 130 | { 131 | let s = ::deserialize(deserializer)?; 132 | Self::from_str(&s).map_err(|err| serde::de::Error::custom(err.to_string())) 133 | } 134 | } 135 | 136 | #[derive(Clone, Eq)] 137 | // This is actually a keypair, because ed25519_dalek api only has keypair.sign 138 | // From ed25519_dalek doc: The first SECRET_KEY_LENGTH of bytes is the SecretKey 139 | // The last PUBLIC_KEY_LENGTH of bytes is the public key, in total it's KEYPAIR_LENGTH 140 | pub struct ED25519SecretKey(pub [u8; ed25519_dalek::KEYPAIR_LENGTH]); 141 | 142 | impl ED25519SecretKey { 143 | pub fn from_secret_key(secret_key: [u8; ed25519_dalek::SECRET_KEY_LENGTH]) -> Self { 144 | Self(ed25519_dalek::SigningKey::from_bytes(&secret_key).to_keypair_bytes()) 145 | } 146 | } 147 | 148 | impl PartialEq for ED25519SecretKey { 149 | fn eq(&self, other: &Self) -> bool { 150 | self.0[..ed25519_dalek::SECRET_KEY_LENGTH] == other.0[..ed25519_dalek::SECRET_KEY_LENGTH] 151 | } 152 | } 153 | 154 | impl std::fmt::Debug for ED25519SecretKey { 155 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { 156 | Display::fmt( 157 | &bs58::encode(&self.0[..ed25519_dalek::SECRET_KEY_LENGTH]).into_string(), 158 | f, 159 | ) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /types/src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::array::TryFromSliceError; 2 | 3 | use near_openapi_types::TxExecutionError; 4 | 5 | use crate::transaction::result::ExecutionFailure; 6 | 7 | #[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)] 8 | pub enum DecimalNumberParsingError { 9 | #[error("Invalid number: {0}")] 10 | InvalidNumber(String), 11 | #[error("Too long whole part: {0}")] 12 | LongWhole(String), 13 | #[error("Too long fractional part: {0}")] 14 | LongFractional(String), 15 | } 16 | 17 | #[derive(thiserror::Error, Debug)] 18 | pub enum KeyTypeError { 19 | #[error("Invalid key format. Expected: [ed25519, secp256k1] but got: {0}")] 20 | InvalidKeyFormat(String), 21 | #[error("Invalid key type byte index: {0}")] 22 | InvalidKeyTypeByteIndex(u8), 23 | } 24 | 25 | #[derive(thiserror::Error, Debug)] 26 | pub enum ParseKeyTypeError { 27 | #[error("Unknown key type: {0}")] 28 | UnknownKeyType(String), 29 | } 30 | 31 | #[derive(thiserror::Error, Debug)] 32 | pub enum DataConversionError { 33 | #[error("Base64 decoding error: {0}")] 34 | Base64DecodingError(#[from] base64::DecodeError), 35 | #[error("Base58 decoding error: {0}")] 36 | Base58DecodingError(#[from] bs58::decode::Error), 37 | #[error("Borsh deserialization error: {0}")] 38 | BorshDeserializationError(#[from] borsh::io::Error), 39 | #[error("JSON deserialization error: {0}")] 40 | JsonDeserializationError(#[from] serde_json::Error), 41 | #[error("Parse int error: {0}")] 42 | ParseIntError(#[from] std::num::ParseIntError), 43 | #[error("Incorrect length: {0}")] 44 | IncorrectLength(usize), 45 | #[error("Invalid public key: {0}")] 46 | InvalidKeyFormat(#[from] KeyTypeError), 47 | #[error("Delegate action is not supported")] 48 | DelegateActionNotSupported, 49 | #[error("Invalid global contract identifier")] 50 | InvalidGlobalContractIdentifier, 51 | } 52 | 53 | impl From> for DataConversionError { 54 | fn from(value: Vec) -> Self { 55 | Self::IncorrectLength(value.len()) 56 | } 57 | } 58 | 59 | impl From for DataConversionError { 60 | fn from(_: TryFromSliceError) -> Self { 61 | Self::IncorrectLength(0) 62 | } 63 | } 64 | 65 | #[derive(thiserror::Error, Debug)] 66 | pub enum ExecutionError { 67 | #[error("Data conversion error: {0}")] 68 | DataConversionError(#[from] DataConversionError), 69 | #[error("Execution failure: {0:?}")] 70 | TransactionFailure(Box), 71 | #[error("EOF while parsing a value at line 1 column 0")] 72 | EofWhileParsingValue, 73 | #[error("Executing transaction failed")] 74 | TransactionExecutionFailed(Box), 75 | #[error("Execution pending or unknown")] 76 | ExecutionPendingOrUnknown, 77 | } 78 | 79 | impl From for ExecutionError { 80 | fn from(value: ExecutionFailure) -> Self { 81 | Self::TransactionFailure(Box::new(value)) 82 | } 83 | } 84 | 85 | impl From for ExecutionError { 86 | fn from(value: TxExecutionError) -> Self { 87 | Self::TransactionExecutionFailed(Box::new(value)) 88 | } 89 | } 90 | 91 | #[derive(thiserror::Error, Debug)] 92 | pub enum SecretKeyError { 93 | #[error("Invalid secret key: {0}")] 94 | InvalidSecp256k1SecretKey(secp256k1::Error), 95 | #[error("Invalid conversion: {0}")] 96 | InvalidConversion(#[from] DataConversionError), 97 | } 98 | 99 | impl From for SecretKeyError { 100 | fn from(value: secp256k1::Error) -> Self { 101 | Self::InvalidSecp256k1SecretKey(value) 102 | } 103 | } 104 | 105 | impl From> for SecretKeyError { 106 | fn from(value: Vec) -> Self { 107 | Self::InvalidConversion(value.into()) 108 | } 109 | } 110 | 111 | impl From for SecretKeyError { 112 | fn from(error: TryFromSliceError) -> Self { 113 | Self::InvalidConversion(error.into()) 114 | } 115 | } 116 | 117 | #[derive(thiserror::Error, Debug)] 118 | pub enum SignatureErrors { 119 | #[error("Invalid signature data: {0}")] 120 | InvalidSignatureData(secp256k1::Error), 121 | } 122 | 123 | impl From for SignatureErrors { 124 | fn from(value: secp256k1::Error) -> Self { 125 | Self::InvalidSignatureData(value) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /types/src/ft.rs: -------------------------------------------------------------------------------- 1 | use crate::json::Base64VecU8; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Clone, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] 6 | pub struct FungibleTokenMetadata { 7 | pub spec: String, 8 | pub name: String, 9 | pub symbol: String, 10 | pub icon: Option, 11 | pub reference: Option, 12 | pub reference_hash: Option, 13 | pub decimals: u8, 14 | } 15 | -------------------------------------------------------------------------------- /types/src/json/integers.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use serde::{Deserialize, Deserializer, Serialize}; 3 | use std::fmt; 4 | 5 | #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize)] 6 | pub struct U64(pub u64); 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, Serialize)] 9 | pub struct U128(pub u128); 10 | 11 | impl From for U64 { 12 | fn from(value: u64) -> Self { 13 | Self(value) 14 | } 15 | } 16 | 17 | impl From for U128 { 18 | fn from(value: u128) -> Self { 19 | Self(value) 20 | } 21 | } 22 | 23 | impl<'de> Deserialize<'de> for U64 { 24 | fn deserialize(deserializer: D) -> Result 25 | where 26 | D: Deserializer<'de>, 27 | { 28 | struct StringOrNumberVisitor; 29 | 30 | impl serde::de::Visitor<'_> for StringOrNumberVisitor { 31 | type Value = U64; 32 | 33 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 34 | formatter.write_str("a string or a number") 35 | } 36 | 37 | fn visit_str(self, value: &str) -> Result 38 | where 39 | E: serde::de::Error, 40 | { 41 | value 42 | .parse::() 43 | .map(U64) 44 | .map_err(serde::de::Error::custom) 45 | } 46 | 47 | fn visit_u64(self, value: u64) -> Result 48 | where 49 | E: serde::de::Error, 50 | { 51 | Ok(U64(value)) 52 | } 53 | } 54 | 55 | deserializer.deserialize_any(StringOrNumberVisitor) 56 | } 57 | } 58 | 59 | impl<'de> Deserialize<'de> for U128 { 60 | fn deserialize(deserializer: D) -> Result 61 | where 62 | D: Deserializer<'de>, 63 | { 64 | struct StringOrNumberVisitor; 65 | 66 | impl serde::de::Visitor<'_> for StringOrNumberVisitor { 67 | type Value = U128; 68 | 69 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 70 | formatter.write_str("a string or a number 128") 71 | } 72 | 73 | fn visit_str(self, value: &str) -> Result 74 | where 75 | E: serde::de::Error, 76 | { 77 | value 78 | .parse::() 79 | .map(U128) 80 | .map_err(serde::de::Error::custom) 81 | } 82 | 83 | fn visit_u64(self, value: u64) -> Result 84 | where 85 | E: serde::de::Error, 86 | { 87 | Ok(U128(value as u128)) 88 | } 89 | 90 | fn visit_u128(self, value: u128) -> Result 91 | where 92 | E: serde::de::Error, 93 | { 94 | Ok(U128(value)) 95 | } 96 | } 97 | 98 | deserializer.deserialize_any(StringOrNumberVisitor) 99 | } 100 | } 101 | 102 | #[cfg(test)] 103 | mod tests { 104 | use super::*; 105 | use borsh::BorshDeserialize; 106 | 107 | #[test] 108 | fn test_u64_struct_from_u64() { 109 | let u64_value = 1234567890; 110 | let u64_from_u64: U64 = u64_value.into(); 111 | 112 | assert_eq!(u64_from_u64.0, u64_value); 113 | } 114 | 115 | #[test] 116 | fn test_u128_struct_from_u128() { 117 | let u128_value = 12345678901234567890; 118 | let u128_from_u128: U128 = u128_value.into(); 119 | 120 | assert_eq!(u128_from_u128.0, u128_value); 121 | } 122 | 123 | #[test] 124 | fn test_u64_struct_from_u128() { 125 | let u128_value = 12345678901234567890; 126 | let u64_from_u128: U64 = u128_value.into(); 127 | 128 | assert_eq!(u64_from_u128.0, u128_value); 129 | } 130 | 131 | #[test] 132 | fn test_u128_struct_from_u64() { 133 | let u64_value = 1234567890; 134 | let u128_from_u64: U128 = u64_value.into(); 135 | 136 | assert_eq!(u128_from_u64.0, u64_value); 137 | } 138 | 139 | #[test] 140 | fn test_u64_serde() { 141 | let u64_value = U64(1234567890); 142 | let serialized = serde_json::to_string(&u64_value).unwrap(); 143 | 144 | assert_eq!(serialized, "1234567890"); 145 | } 146 | 147 | #[test] 148 | fn test_u128_serde() { 149 | let u128_value = U128(12345678901234567890); 150 | let serialized = serde_json::to_string(&u128_value).unwrap(); 151 | 152 | assert_eq!(serialized, "12345678901234567890"); 153 | } 154 | 155 | #[test] 156 | fn test_u64_from_str() { 157 | let u64_value = "12345678901234567890"; 158 | let deserialized: U64 = serde_json::from_str(u64_value).unwrap(); 159 | 160 | assert_eq!(deserialized, U64(12345678901234567890)); 161 | } 162 | 163 | #[test] 164 | fn test_u128_from_str() { 165 | let u128_value = "12345678901234567890"; 166 | let deserialized: U128 = serde_json::from_str(u128_value).unwrap(); 167 | 168 | assert_eq!(deserialized, U128(12345678901234567890)); 169 | } 170 | 171 | #[test] 172 | fn test_u64_de_serde() { 173 | let u64_value = 1234567890; 174 | let u64_value_str = format!("\"{u64_value}\""); 175 | let deserialized: U64 = serde_json::from_str(&u64_value_str).unwrap(); 176 | 177 | assert_eq!(deserialized.0, u64_value); 178 | } 179 | 180 | #[test] 181 | fn test_u128_de_serde() { 182 | let u128_value = 12345678901234567890; 183 | let u128_value_str = format!("\"{u128_value}\""); 184 | let deserialized: U128 = serde_json::from_str(&u128_value_str).unwrap(); 185 | 186 | assert_eq!(deserialized.0, u128_value); 187 | } 188 | 189 | #[test] 190 | fn test_u64_borsh() { 191 | let u64_value = U64(1234567890); 192 | let serialized = borsh::to_vec(&u64_value).unwrap(); 193 | let deserialized = U64::try_from_slice(&serialized).unwrap(); 194 | 195 | assert_eq!(deserialized, u64_value); 196 | } 197 | 198 | #[test] 199 | fn test_u128_borsh() { 200 | let u128_value = U128(12345678901234567890u128); 201 | let serialized = borsh::to_vec(&u128_value).unwrap(); 202 | let deserialized = U128::try_from_slice(&serialized).unwrap(); 203 | 204 | assert_eq!(deserialized, u128_value); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /types/src/json/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod integers; 2 | pub mod vector; 3 | 4 | pub use integers::*; 5 | pub use vector::*; 6 | -------------------------------------------------------------------------------- /types/src/json/vector.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::utils::base64_bytes; 5 | 6 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] 7 | pub struct Base64VecU8(#[serde(with = "base64_bytes")] pub Vec); 8 | 9 | impl From> for Base64VecU8 { 10 | fn from(v: Vec) -> Self { 11 | Self(v) 12 | } 13 | } 14 | 15 | impl From for Vec { 16 | fn from(v: Base64VecU8) -> Self { 17 | v.0 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /types/src/lib.rs: -------------------------------------------------------------------------------- 1 | use sha2::Digest; 2 | use std::fmt; 3 | 4 | pub mod account; 5 | pub mod contract; 6 | pub mod crypto; 7 | pub mod errors; 8 | pub mod ft; 9 | pub mod json; 10 | pub mod nft; 11 | pub mod reference; 12 | pub mod signable_message; 13 | pub mod stake; 14 | pub mod storage; 15 | pub mod tokens; 16 | pub mod transaction; 17 | pub mod utils; 18 | 19 | pub use near_abi as abi; 20 | pub use near_account_id::AccountId; 21 | pub use near_gas::NearGas; 22 | pub use near_openapi_types::{ 23 | AccountView, ContractCodeView, FunctionArgs, RpcBlockResponse, RpcTransactionResponse, 24 | RpcValidatorResponse, StoreKey, StoreValue, TxExecutionStatus, ViewStateResult, 25 | }; 26 | pub use near_token::NearToken; 27 | pub use reference::{EpochReference, Reference}; 28 | pub use storage::{StorageBalance, StorageBalanceInternal}; 29 | 30 | pub use account::Account; 31 | pub use crypto::public_key::PublicKey; 32 | pub use crypto::secret_key::SecretKey; 33 | pub use crypto::signature::Signature; 34 | pub use transaction::actions::{AccessKey, AccessKeyPermission, Action}; 35 | 36 | use crate::errors::DataConversionError; 37 | 38 | pub type BlockHeight = u64; 39 | pub type Nonce = u64; 40 | pub type StorageUsage = u64; 41 | 42 | /// A wrapper around a generic query result that includes the block height and block hash 43 | /// at which the query was executed 44 | #[derive( 45 | Debug, 46 | Clone, 47 | serde::Serialize, 48 | serde::Deserialize, 49 | borsh::BorshDeserialize, 50 | borsh::BorshSerialize, 51 | )] 52 | pub struct Data { 53 | /// The data returned by the query 54 | pub data: T, 55 | /// The block height at which the query was executed 56 | pub block_height: BlockHeight, 57 | /// The block hash at which the query was executed 58 | pub block_hash: CryptoHash, 59 | } 60 | 61 | impl Data { 62 | pub fn map(self, f: impl FnOnce(T) -> U) -> Data { 63 | Data { 64 | data: f(self.data), 65 | block_height: self.block_height, 66 | block_hash: self.block_hash, 67 | } 68 | } 69 | } 70 | 71 | /// A type that represents a hash of the data. 72 | /// 73 | /// This type is copy of the [crate::CryptoHash] 74 | /// as part of the [decoupling initiative](https://github.com/near/near-api-rs/issues/5) 75 | #[derive( 76 | Copy, 77 | Clone, 78 | Default, 79 | Hash, 80 | Eq, 81 | PartialEq, 82 | Ord, 83 | PartialOrd, 84 | borsh::BorshDeserialize, 85 | borsh::BorshSerialize, 86 | )] 87 | pub struct CryptoHash(pub [u8; 32]); 88 | 89 | impl serde::Serialize for CryptoHash { 90 | fn serialize(&self, serializer: S) -> Result 91 | where 92 | S: serde::Serializer, 93 | { 94 | serializer.serialize_str(&self.to_string()) 95 | } 96 | } 97 | 98 | impl<'de> serde::Deserialize<'de> for CryptoHash { 99 | fn deserialize(deserializer: D) -> Result 100 | where 101 | D: serde::Deserializer<'de>, 102 | { 103 | let s = String::deserialize(deserializer)?; 104 | ::from_str(&s).map_err(serde::de::Error::custom) 105 | } 106 | } 107 | 108 | impl CryptoHash { 109 | pub fn hash(bytes: &[u8]) -> Self { 110 | Self(sha2::Sha256::digest(bytes).into()) 111 | } 112 | } 113 | 114 | impl std::str::FromStr for CryptoHash { 115 | type Err = DataConversionError; 116 | 117 | fn from_str(s: &str) -> Result { 118 | let bytes = bs58::decode(s).into_vec()?; 119 | Self::try_from(bytes) 120 | } 121 | } 122 | 123 | impl TryFrom<&[u8]> for CryptoHash { 124 | type Error = DataConversionError; 125 | 126 | fn try_from(bytes: &[u8]) -> Result { 127 | if bytes.len() != 32 { 128 | return Err(DataConversionError::IncorrectLength(bytes.len())); 129 | } 130 | let mut buf = [0; 32]; 131 | buf.copy_from_slice(bytes); 132 | Ok(Self(buf)) 133 | } 134 | } 135 | 136 | impl TryFrom> for CryptoHash { 137 | type Error = DataConversionError; 138 | 139 | fn try_from(v: Vec) -> Result { 140 | >::try_from(v.as_ref()) 141 | } 142 | } 143 | 144 | impl TryFrom for CryptoHash { 145 | type Error = DataConversionError; 146 | 147 | fn try_from(value: near_openapi_types::CryptoHash) -> Result { 148 | let near_openapi_types::CryptoHash(hash) = value; 149 | let bytes = bs58::decode(hash).into_vec()?; 150 | Self::try_from(bytes) 151 | } 152 | } 153 | 154 | impl std::fmt::Debug for CryptoHash { 155 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 156 | write!(f, "{self}") 157 | } 158 | } 159 | 160 | impl std::fmt::Display for CryptoHash { 161 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 162 | std::fmt::Display::fmt(&bs58::encode(self.0).into_string(), f) 163 | } 164 | } 165 | 166 | impl From for near_openapi_types::CryptoHash { 167 | fn from(hash: CryptoHash) -> Self { 168 | Self(hash.to_string()) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /types/src/nft.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use borsh::{BorshDeserialize, BorshSerialize}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::{AccountId, json::Base64VecU8}; 7 | 8 | pub type TokenId = String; 9 | 10 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize, BorshSerialize)] 11 | pub struct NFTContractMetadata { 12 | pub spec: String, // required, essentially a version like "nft-1.0.0" 13 | pub name: String, // required, ex. "Mosaics" 14 | pub symbol: String, // required, ex. "MOSAIC" 15 | pub icon: Option, // Data URL 16 | pub base_uri: Option, // Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs 17 | pub reference: Option, // URL to a JSON file with more info 18 | pub reference_hash: Option, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. 19 | } 20 | 21 | #[derive( 22 | Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize, BorshSerialize, 23 | )] 24 | pub struct TokenMetadata { 25 | pub title: Option, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" 26 | pub description: Option, // free-form description 27 | pub media: Option, // URL to associated media, preferably to decentralized, content-addressed storage 28 | pub media_hash: Option, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. 29 | pub copies: Option, // number of copies of this set of metadata in existence when token was minted. 30 | pub issued_at: Option, // ISO 8601 datetime when token was issued or minted 31 | pub expires_at: Option, // ISO 8601 datetime when token expires 32 | pub starts_at: Option, // ISO 8601 datetime when token starts being valid 33 | pub updated_at: Option, // ISO 8601 datetime when token was last updated 34 | pub extra: Option, // anything extra the NFT wants to store on-chain. Can be stringified JSON. 35 | pub reference: Option, // URL to an off-chain JSON file with more info. 36 | pub reference_hash: Option, // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. 37 | } 38 | 39 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)] 40 | pub struct Token { 41 | pub token_id: TokenId, 42 | pub owner_id: AccountId, 43 | pub metadata: Option, 44 | pub approved_account_ids: Option>, 45 | } 46 | -------------------------------------------------------------------------------- /types/src/reference.rs: -------------------------------------------------------------------------------- 1 | // Source: 2 | 3 | use crate::{BlockHeight, CryptoHash}; 4 | 5 | /// A reference to a specific block. This type is used to specify the block for most queries. 6 | /// 7 | /// It represents the finality of a transaction or block in which transaction is included in. For more info 8 | /// go to the [NEAR finality](https://docs.near.org/docs/concepts/transaction#finality) docs. 9 | #[derive(Clone, Debug)] 10 | pub enum Reference { 11 | /// Optimistic finality. The latest block recorded on the node that responded to our query 12 | /// (<1 second delay after the transaction is submitted). 13 | Optimistic, 14 | /// Near-final finality. Similarly to `Final` finality, but delay should be roughly 1 second. 15 | NearFinal, 16 | /// Final finality. The block that has been validated on at least 66% of the nodes in the 17 | /// network. (At max, should be 2 second delay after the transaction is submitted.) 18 | Final, 19 | /// Reference to a specific block. 20 | AtBlock(BlockHeight), 21 | /// Reference to a specific block hash. 22 | AtBlockHash(CryptoHash), 23 | } 24 | 25 | /// A reference to a specific epoch. This type is used to specify the epoch for some queries. 26 | #[derive(Clone, Debug)] 27 | pub enum EpochReference { 28 | /// Reference to a specific Epoch Id 29 | AtEpoch(CryptoHash), 30 | /// Reference to an epoch at a specific block height. 31 | AtBlock(BlockHeight), 32 | /// Reference to an epoch at a specific block hash. 33 | AtBlockHash(CryptoHash), 34 | /// Latest epoch on the node 35 | Latest, 36 | } 37 | -------------------------------------------------------------------------------- /types/src/signable_message.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | 3 | /// Used to distinguish message types that are sign by account keys, to avoid an 4 | /// abuse of signed messages as something else. 5 | /// 6 | /// This prefix must be at the first four bytes of a message body that is 7 | /// signed under this signature scheme. 8 | /// 9 | /// The scheme is a draft introduced to avoid security issues with the 10 | /// implementation of meta transactions (NEP-366) but will eventually be 11 | /// standardized with NEP-461 that solves the problem more generally. 12 | #[derive( 13 | Debug, 14 | Clone, 15 | Copy, 16 | PartialEq, 17 | Eq, 18 | PartialOrd, 19 | Ord, 20 | Hash, 21 | BorshDeserialize, 22 | BorshSerialize, 23 | serde::Serialize, 24 | serde::Deserialize, 25 | )] 26 | pub struct MessageDiscriminant { 27 | /// The unique prefix, serialized in little-endian by borsh. 28 | discriminant: u32, 29 | } 30 | 31 | const MIN_ON_CHAIN_DISCRIMINANT: u32 = 1 << 30; 32 | const NEP_366_META_TRANSACTIONS: u32 = MIN_ON_CHAIN_DISCRIMINANT + 366; 33 | 34 | /// A wrapper around a message that should be signed using this scheme. 35 | /// 36 | /// Only used for constructing a signature, not used to transmit messages. The 37 | /// discriminant prefix is implicit and should be known by the receiver based on 38 | /// the context in which the message is received. 39 | #[derive(BorshSerialize)] 40 | pub struct SignableMessage<'a, T> { 41 | pub discriminant: MessageDiscriminant, 42 | pub msg: &'a T, 43 | } 44 | 45 | impl<'a, T: BorshSerialize> SignableMessage<'a, T> { 46 | pub fn new(msg: &'a T, ty: SignableMessageType) -> Self { 47 | let discriminant = ty.into(); 48 | Self { discriminant, msg } 49 | } 50 | } 51 | 52 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 53 | #[non_exhaustive] 54 | pub enum SignableMessageType { 55 | /// A delegate action, intended for a relayer to included it in an action list of a transaction. 56 | DelegateAction, 57 | } 58 | 59 | impl From for MessageDiscriminant { 60 | fn from(ty: SignableMessageType) -> Self { 61 | // unwrapping here is ok, we know the constant NEP numbers used are in range 62 | match ty { 63 | SignableMessageType::DelegateAction => Self { 64 | discriminant: NEP_366_META_TRANSACTIONS, 65 | }, 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /types/src/stake.rs: -------------------------------------------------------------------------------- 1 | use near_account_id::AccountId; 2 | use near_token::NearToken; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Aggregate information about the staking pool. 6 | /// 7 | /// The type is related to the [StakingPool](https://github.com/near/core-contracts/tree/master/staking-pool) smart contract. 8 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 9 | pub struct StakingPoolInfo { 10 | /// The validator that is running the pool. 11 | pub validator_id: AccountId, 12 | /// The fee that is taken by the pool contract. 13 | pub fee: Option, 14 | /// The number of delegators on the pool. 15 | pub delegators: Option, 16 | /// The total staked balance on the pool (by all delegators). 17 | pub stake: NearToken, 18 | } 19 | 20 | /// The reward fee that is taken by the pool contract. 21 | /// 22 | /// This represents the percentage of the reward that is taken by the pool contract. 23 | /// The type is a part of the [StakingPool](https://github.com/near/core-contracts/tree/master/staking-pool) interface 24 | /// 25 | /// The fraction is equal to numerator/denominator, e.g. 3/1000 = 0.3% 26 | #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] 27 | pub struct RewardFeeFraction { 28 | /// The numerator of the fraction. 29 | pub numerator: u32, 30 | /// The denominator of the fraction. 31 | pub denominator: u32, 32 | } 33 | 34 | /// The total user balance on a pool contract 35 | /// 36 | /// The type is related to the [StakingPool](https://github.com/near/core-contracts/tree/master/staking-pool) smart contract. 37 | #[derive(Debug, Clone, PartialEq, Default, Eq, Serialize, Deserialize)] 38 | pub struct UserStakeBalance { 39 | /// The balance that currently is staked. The user can't withdraw this balance until `unstake` is called 40 | /// and withdraw period is over. 41 | pub staked: NearToken, 42 | /// The balance that is not staked. The user can start withdrawing this balance. Some pools 43 | /// have a withdraw period. 44 | pub unstaked: NearToken, 45 | /// The total balance of the user on a contract (staked + unstaked) 46 | pub total: NearToken, 47 | } 48 | -------------------------------------------------------------------------------- /types/src/storage.rs: -------------------------------------------------------------------------------- 1 | use crate::NearToken; 2 | use serde::de::{Deserialize, Deserializer}; 3 | 4 | /// A type that represents the storage balance. Please note that this type is not part of the NEP-145 standard. 5 | /// This type provides a more detailed view of the storage balance on the contract. 6 | /// 7 | /// [StorageBalanceInternal] is a [NEP-145](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) standard type. 8 | /// This type is used internally to parse the storage balance from the contract and 9 | /// to convert it into the [StorageBalance] type. 10 | /// 11 | /// As a storing data on-chain requires storage staking, the contracts require users to deposit NEAR to store user-rel. 12 | #[derive(Debug, Clone)] 13 | pub struct StorageBalance { 14 | /// The available balance that might be used for storage. 15 | /// 16 | /// The user can withdraw this balance from the contract. 17 | pub available: NearToken, 18 | /// The total user balance on the contract for storage. 19 | /// 20 | /// This is a sum of the `available` and `locked` balances. 21 | pub total: NearToken, 22 | 23 | /// The storage deposit that is locked for the account 24 | /// 25 | /// The user can unlock some funds by removing the data from the contract. 26 | /// Though, it's contract-specific on how much can be unlocked. 27 | pub locked: NearToken, 28 | } 29 | 30 | /// Used internally to parse the storage balance from the contract and 31 | /// to convert it into the [StorageBalance] type. 32 | /// 33 | /// This type is a part of the [NEP-145](https://github.com/near/NEPs/blob/master/neps/nep-0145.md) standard. 34 | #[derive(Debug, Clone, serde::Deserialize)] 35 | pub struct StorageBalanceInternal { 36 | #[serde(deserialize_with = "parse_u128_string")] 37 | pub available: NearToken, 38 | #[serde(deserialize_with = "parse_u128_string")] 39 | pub total: NearToken, 40 | } 41 | 42 | fn parse_u128_string<'de, D>(deserializer: D) -> Result 43 | where 44 | D: Deserializer<'de>, 45 | { 46 | ::deserialize(deserializer)? 47 | .parse::() 48 | .map(NearToken::from_yoctonear) 49 | .map_err(serde::de::Error::custom) 50 | } 51 | -------------------------------------------------------------------------------- /types/src/transaction/delegate_action.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use base64::{Engine, prelude::BASE64_STANDARD}; 4 | use borsh::{BorshDeserialize, BorshSerialize}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::{ 8 | AccountId, Action, BlockHeight, Nonce, PublicKey, Signature, errors::DataConversionError, 9 | }; 10 | 11 | #[derive(Debug, Clone, BorshDeserialize, BorshSerialize, Serialize, Deserialize, PartialEq, Eq)] 12 | pub struct NonDelegateAction(Action); 13 | 14 | impl TryFrom for NonDelegateAction { 15 | type Error = (); 16 | fn try_from(action: Action) -> Result { 17 | if let Action::Delegate(_) = action { 18 | return Err(()); 19 | } 20 | Ok(Self(action)) 21 | } 22 | } 23 | 24 | #[derive(Debug, Clone, BorshDeserialize, BorshSerialize, Serialize, Deserialize, PartialEq, Eq)] 25 | pub struct DelegateAction { 26 | pub sender_id: AccountId, 27 | pub receiver_id: AccountId, 28 | pub actions: Vec, 29 | pub nonce: Nonce, 30 | pub max_block_height: BlockHeight, 31 | pub public_key: PublicKey, 32 | } 33 | 34 | impl TryFrom for DelegateAction { 35 | type Error = DataConversionError; 36 | fn try_from(value: near_openapi_types::DelegateAction) -> Result { 37 | let near_openapi_types::DelegateAction { 38 | sender_id, 39 | receiver_id, 40 | actions, 41 | nonce, 42 | max_block_height, 43 | public_key, 44 | } = value; 45 | 46 | Ok(Self { 47 | sender_id, 48 | receiver_id, 49 | actions: actions 50 | .into_iter() 51 | .map(NonDelegateAction::try_from) 52 | .collect::, _>>()?, 53 | nonce, 54 | max_block_height, 55 | public_key: public_key.try_into()?, 56 | }) 57 | } 58 | } 59 | 60 | impl TryFrom for NonDelegateAction { 61 | type Error = DataConversionError; 62 | fn try_from(val: near_openapi_types::NonDelegateAction) -> Result { 63 | match val { 64 | near_openapi_types::NonDelegateAction::DeterministicStateInit( 65 | deterministic_state_init, 66 | ) => Ok(Self(Action::DeterministicStateInit(Box::new( 67 | deterministic_state_init.try_into()?, 68 | )))), 69 | near_openapi_types::NonDelegateAction::CreateAccount(create_account_action) => { 70 | Ok(Self(Action::CreateAccount(create_account_action.into()))) 71 | } 72 | near_openapi_types::NonDelegateAction::DeployContract(deploy_contract_action) => Ok( 73 | Self(Action::DeployContract(deploy_contract_action.try_into()?)), 74 | ), 75 | near_openapi_types::NonDelegateAction::FunctionCall(function_call_action) => Ok(Self( 76 | Action::FunctionCall(Box::new(function_call_action.try_into()?)), 77 | )), 78 | near_openapi_types::NonDelegateAction::Transfer(transfer_action) => { 79 | Ok(Self(Action::Transfer(transfer_action.try_into()?))) 80 | } 81 | near_openapi_types::NonDelegateAction::Stake(stake_action) => { 82 | Ok(Self(Action::Stake(Box::new(stake_action.try_into()?)))) 83 | } 84 | near_openapi_types::NonDelegateAction::AddKey(add_key_action) => { 85 | Ok(Self(Action::AddKey(Box::new(add_key_action.try_into()?)))) 86 | } 87 | near_openapi_types::NonDelegateAction::DeleteKey(delete_key_action) => Ok(Self( 88 | Action::DeleteKey(Box::new(delete_key_action.try_into()?)), 89 | )), 90 | near_openapi_types::NonDelegateAction::DeleteAccount(delete_account_action) => { 91 | Ok(Self(Action::DeleteAccount(delete_account_action.into()))) 92 | } 93 | near_openapi_types::NonDelegateAction::DeployGlobalContract( 94 | deploy_global_contract_action, 95 | ) => Ok(Self(Action::DeployGlobalContract( 96 | deploy_global_contract_action.try_into()?, 97 | ))), 98 | near_openapi_types::NonDelegateAction::UseGlobalContract( 99 | use_global_contract_action, 100 | ) => Ok(Self(Action::UseGlobalContract(Box::new( 101 | use_global_contract_action.try_into()?, 102 | )))), 103 | } 104 | } 105 | } 106 | 107 | #[derive(Debug, Clone, BorshDeserialize, BorshSerialize, Serialize, Deserialize, PartialEq, Eq)] 108 | pub struct SignedDelegateAction { 109 | pub delegate_action: DelegateAction, 110 | pub signature: Signature, 111 | } 112 | 113 | impl TryFrom for SignedDelegateAction { 114 | type Error = DataConversionError; 115 | fn try_from(value: near_openapi_types::SignedDelegateAction) -> Result { 116 | let near_openapi_types::SignedDelegateAction { 117 | delegate_action, 118 | signature, 119 | } = value; 120 | Ok(Self { 121 | delegate_action: delegate_action.try_into()?, 122 | signature: Signature::from_str(&signature)?, 123 | }) 124 | } 125 | } 126 | 127 | /// A wrapper around [crate::transaction::delegate_action::SignedDelegateAction] that allows for easy serialization and deserialization as base64 string 128 | /// 129 | /// The type implements [std::str::FromStr] and [std::fmt::Display] to serialize and deserialize the type as base64 string 130 | #[derive(Debug, Clone)] 131 | pub struct SignedDelegateActionAsBase64 { 132 | /// The inner signed delegate action 133 | pub inner: SignedDelegateAction, 134 | } 135 | 136 | impl std::str::FromStr for SignedDelegateActionAsBase64 { 137 | type Err = DataConversionError; 138 | fn from_str(s: &str) -> Result { 139 | Ok(Self { 140 | inner: borsh::from_slice(&bs58::decode(s).into_vec()?)?, 141 | }) 142 | } 143 | } 144 | 145 | impl std::fmt::Display for SignedDelegateActionAsBase64 { 146 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 147 | let base64_signed_delegate_action = BASE64_STANDARD.encode( 148 | borsh::to_vec(&self.inner) 149 | .expect("Signed Delegate Action serialization to borsh is not expected to fail"), 150 | ); 151 | write!(f, "{base64_signed_delegate_action}") 152 | } 153 | } 154 | 155 | impl From for SignedDelegateActionAsBase64 { 156 | fn from(value: SignedDelegateAction) -> Self { 157 | Self { inner: value } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /types/src/transaction/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::OnceCell, io::Write, str::FromStr}; 2 | 3 | pub mod actions; 4 | pub mod delegate_action; 5 | pub mod result; 6 | 7 | use base64::{Engine, prelude::BASE64_STANDARD}; 8 | use borsh::{BorshDeserialize, BorshSerialize}; 9 | use serde::{Deserialize, Serialize}; 10 | 11 | use crate::{ 12 | AccountId, Action, CryptoHash, Nonce, PublicKey, Signature, errors::DataConversionError, 13 | }; 14 | 15 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] 16 | pub struct TransactionV0 { 17 | pub signer_id: AccountId, 18 | pub public_key: PublicKey, 19 | pub nonce: Nonce, 20 | pub receiver_id: AccountId, 21 | pub block_hash: CryptoHash, 22 | pub actions: Vec, 23 | } 24 | 25 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] 26 | pub struct TransactionV1 { 27 | pub signer_id: AccountId, 28 | pub public_key: PublicKey, 29 | pub nonce: Nonce, 30 | pub receiver_id: AccountId, 31 | pub block_hash: CryptoHash, 32 | pub actions: Vec, 33 | pub priority_fee: u64, 34 | } 35 | 36 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshDeserialize)] 37 | pub enum Transaction { 38 | V0(TransactionV0), 39 | V1(TransactionV1), 40 | } 41 | 42 | impl Transaction { 43 | pub const fn signer_id(&self) -> &AccountId { 44 | match self { 45 | Self::V0(tx) => &tx.signer_id, 46 | Self::V1(tx) => &tx.signer_id, 47 | } 48 | } 49 | 50 | pub const fn receiver_id(&self) -> &AccountId { 51 | match self { 52 | Self::V0(tx) => &tx.receiver_id, 53 | Self::V1(tx) => &tx.receiver_id, 54 | } 55 | } 56 | 57 | pub const fn nonce(&self) -> Nonce { 58 | match self { 59 | Self::V0(tx) => tx.nonce, 60 | Self::V1(tx) => tx.nonce, 61 | } 62 | } 63 | 64 | pub const fn public_key(&self) -> &PublicKey { 65 | match self { 66 | Self::V0(tx) => &tx.public_key, 67 | Self::V1(tx) => &tx.public_key, 68 | } 69 | } 70 | 71 | pub fn actions(&self) -> &[Action] { 72 | match self { 73 | Self::V0(tx) => &tx.actions, 74 | Self::V1(tx) => &tx.actions, 75 | } 76 | } 77 | 78 | pub const fn actions_mut(&mut self) -> &mut Vec { 79 | match self { 80 | Self::V0(tx) => &mut tx.actions, 81 | Self::V1(tx) => &mut tx.actions, 82 | } 83 | } 84 | 85 | pub fn take_actions(&mut self) -> Vec { 86 | let actions = match self { 87 | Self::V0(tx) => &mut tx.actions, 88 | Self::V1(tx) => &mut tx.actions, 89 | }; 90 | std::mem::take(actions) 91 | } 92 | 93 | pub fn get_hash(&self) -> CryptoHash { 94 | let bytes = borsh::to_vec(&self).expect("Failed to deserialize"); 95 | CryptoHash::hash(&bytes) 96 | } 97 | } 98 | 99 | impl BorshSerialize for Transaction { 100 | fn serialize(&self, writer: &mut W) -> Result<(), std::io::Error> { 101 | match self { 102 | Self::V0(tx) => BorshSerialize::serialize(tx, writer)?, 103 | Self::V1(tx) => { 104 | BorshSerialize::serialize(&1_u8, writer)?; 105 | BorshSerialize::serialize(tx, writer)?; 106 | } 107 | } 108 | Ok(()) 109 | } 110 | } 111 | 112 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] 113 | pub struct SignedTransaction { 114 | pub transaction: Transaction, 115 | pub signature: Signature, 116 | #[borsh(skip)] 117 | #[serde(skip)] 118 | hash: OnceCell, 119 | } 120 | 121 | impl TryFrom for SignedTransaction { 122 | type Error = DataConversionError; 123 | 124 | fn try_from(value: near_openapi_types::SignedTransactionView) -> Result { 125 | let near_openapi_types::SignedTransactionView { 126 | signer_id, 127 | public_key, 128 | nonce, 129 | receiver_id, 130 | actions, 131 | priority_fee, 132 | hash, 133 | signature, 134 | } = value; 135 | 136 | let transaction = if priority_fee > 0 { 137 | Transaction::V1(TransactionV1 { 138 | signer_id, 139 | public_key: public_key.try_into()?, 140 | nonce, 141 | receiver_id, 142 | block_hash: hash.try_into()?, 143 | actions: actions 144 | .into_iter() 145 | .map(Action::try_from) 146 | .collect::, _>>()?, 147 | priority_fee, 148 | }) 149 | } else { 150 | Transaction::V0(TransactionV0 { 151 | signer_id, 152 | public_key: public_key.try_into()?, 153 | nonce, 154 | receiver_id, 155 | block_hash: hash.try_into()?, 156 | actions: actions 157 | .into_iter() 158 | .map(Action::try_from) 159 | .collect::, _>>()?, 160 | }) 161 | }; 162 | 163 | Ok(Self::new(Signature::from_str(&signature)?, transaction)) 164 | } 165 | } 166 | 167 | impl From for near_openapi_types::SignedTransaction { 168 | fn from(tr: SignedTransaction) -> Self { 169 | let bytes = borsh::to_vec(&tr).expect("Failed to serialize"); 170 | Self(BASE64_STANDARD.encode(bytes)) 171 | } 172 | } 173 | 174 | impl From for PrepopulateTransaction { 175 | fn from(mut tr: SignedTransaction) -> Self { 176 | Self { 177 | signer_id: tr.transaction.signer_id().clone(), 178 | receiver_id: tr.transaction.receiver_id().clone(), 179 | actions: tr.transaction.take_actions(), 180 | } 181 | } 182 | } 183 | 184 | impl SignedTransaction { 185 | pub const fn new(signature: Signature, transaction: Transaction) -> Self { 186 | Self { 187 | signature, 188 | transaction, 189 | hash: OnceCell::new(), 190 | } 191 | } 192 | 193 | pub fn get_hash(&self) -> CryptoHash { 194 | *self.hash.get_or_init(|| self.transaction.get_hash()) 195 | } 196 | } 197 | 198 | /// An internal type that represents unsigned transaction. 199 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 200 | pub struct PrepopulateTransaction { 201 | /// The account that will sign the transaction. 202 | pub signer_id: near_account_id::AccountId, 203 | /// The account that will receive the transaction 204 | pub receiver_id: near_account_id::AccountId, 205 | /// The actions that will be executed by the transaction. 206 | pub actions: Vec, 207 | } 208 | -------------------------------------------------------------------------------- /types/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | /// Convenience module to allow annotating a serde structure as base64 bytes. 2 | pub mod base64_bytes { 3 | use base64::Engine; 4 | use serde::{Deserialize, Deserializer, Serializer, de}; 5 | 6 | pub fn serialize(bytes: &[u8], serializer: S) -> Result 7 | where 8 | S: Serializer, 9 | { 10 | serializer.serialize_str(&base64::engine::general_purpose::STANDARD.encode(bytes)) 11 | } 12 | 13 | pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> 14 | where 15 | D: Deserializer<'de>, 16 | { 17 | let s: String = Deserialize::deserialize(deserializer)?; 18 | base64::engine::general_purpose::STANDARD 19 | .decode(s.as_str()) 20 | .map_err(de::Error::custom) 21 | } 22 | } 23 | 24 | pub mod near_gas_as_u64 { 25 | use near_gas::NearGas; 26 | use serde::Serializer; 27 | 28 | pub fn serialize(value: &NearGas, serializer: S) -> Result 29 | where 30 | S: Serializer, 31 | { 32 | serializer.serialize_u64(value.as_gas()) 33 | } 34 | } 35 | --------------------------------------------------------------------------------