├── .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 |
4 |
5 |
6 |
7 |
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