├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .travis.yml ├── LICENSE ├── LICENSE-APACHE ├── README.md ├── contract-rs ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.sh ├── res │ └── transfer_faucet.wasm └── src │ └── lib.rs └── frontend ├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.js ├── App.test.js ├── index.js └── setupTests.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Build files 2 | 3 | contract-rs/target 4 | **/target/ 5 | **/pkg/ 6 | 7 | # IDE 8 | .idea 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # Key pairs generated by the NEAR shell 14 | neardev 15 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | 3 | RUN bash -cl "rustup toolchain install stable && rustup target add wasm32-unknown-unknown" 4 | 5 | RUN bash -c ". .nvm/nvm.sh \ 6 | && nvm install v12 && nvm alias default v12" -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | # Options to prebuild the image after github events and set notifications/badges 4 | github: 5 | prebuilds: 6 | # enable for the master/default branch (defaults to true) 7 | master: true 8 | # enable for all branches in this repo (defaults to false) 9 | branches: true 10 | # enable for pull requests coming from this repo (defaults to true) 11 | pullRequests: true 12 | # enable for pull requests coming from forks (defaults to false) 13 | pullRequestsFromForks: true 14 | # add a check to pull requests (defaults to true) 15 | addCheck: true 16 | # add a "Review in Gitpod" button as a comment to pull requests (defaults to false) 17 | addComment: true 18 | # add a "Review in Gitpod" button to the pull request's description (defaults to false) 19 | addBadge: false 20 | # add a label once the prebuild is ready to pull requests (defaults to false) 21 | addLabel: false 22 | 23 | # List the start up tasks. You can start them in parallel in multiple terminals. See https://www.gitpod.io/docs/config-start-tasks/ 24 | tasks: 25 | - before: nvm use default 26 | 27 | vscode: 28 | extensions: 29 | - hbenl.vscode-test-explorer@2.15.0:koqDUMWDPJzELp/hdS/lWw== 30 | - Swellaby.vscode-rust-test-adapter@0.11.0:Xg+YeZZQiVpVUsIkH+uiiw== 31 | - bungcip.better-toml@0.3.2:3QfgGxxYtGHfJKQU7H0nEw== 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | before_install: 5 | - rustup target add wasm32-unknown-unknown 6 | script: 7 | - cd contract-rs 8 | - ./build.sh 9 | - cargo test --verbose --all -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 NEAR Inc 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Proof of Work Transfer Faucet 2 | ============================= 3 | 4 | 5 | 6 | Try it out: https://near-examples.github.io/token-printer/ 7 | 8 | It consists of 2 parts: 9 | 10 | ## Transfer Faucet contract 11 | 12 | A Faucet contract allows to transfer tokens to a desired account for doing required Proof of Work. 13 | This contract is based on PoW faucet example: https://github.com/near-examples/pow-faucet 14 | 15 | The transfer amount is set to 100N tokens. It's enough to deploy a 1Mb contract. 16 | 17 | Proof of Work works the following way: 18 | 19 | You need to compute a u64 salt (nonce) for a given account in such a way 20 | that the `sha256(account_id + ':' + salt)` has at the amount of leading zero bits as 21 | the required `min_difficulty`. The hash has to be unique in order to receive transfer. 22 | One account can request multiple transfers. 23 | 24 | ## Faucet frontend 25 | 26 | Allows to enter the account ID to receive transfer. And it computes the Proof of Work required by the contract using front-end JS. 27 | 28 | https://near-examples.github.io/token-printer/ 29 | 30 | ## Testing 31 | To test run: 32 | ```bash 33 | cargo test 34 | ``` 35 | -------------------------------------------------------------------------------- /contract-rs/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "Inflector" 5 | version = "0.11.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 8 | dependencies = [ 9 | "lazy_static", 10 | "regex", 11 | ] 12 | 13 | [[package]] 14 | name = "aho-corasick" 15 | version = "0.7.10" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" 18 | dependencies = [ 19 | "memchr", 20 | ] 21 | 22 | [[package]] 23 | name = "autocfg" 24 | version = "1.0.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 27 | 28 | [[package]] 29 | name = "base64" 30 | version = "0.11.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 33 | 34 | [[package]] 35 | name = "block-buffer" 36 | version = "0.7.3" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 39 | dependencies = [ 40 | "block-padding", 41 | "byte-tools", 42 | "byteorder", 43 | "generic-array", 44 | ] 45 | 46 | [[package]] 47 | name = "block-padding" 48 | version = "0.1.5" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 51 | dependencies = [ 52 | "byte-tools", 53 | ] 54 | 55 | [[package]] 56 | name = "borsh" 57 | version = "0.6.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "2f9dada4c07fa726bc195503048581e7b1719407f7fbef82741f7b149d3921b3" 60 | dependencies = [ 61 | "borsh-derive", 62 | ] 63 | 64 | [[package]] 65 | name = "borsh-derive" 66 | version = "0.6.1" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "47c6bed3dd7695230e85bd51b6a4e4e4dc7550c1974a79c11e98a8a055211a61" 69 | dependencies = [ 70 | "borsh-derive-internal", 71 | "borsh-schema-derive-internal", 72 | "syn", 73 | ] 74 | 75 | [[package]] 76 | name = "borsh-derive-internal" 77 | version = "0.6.1" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "d34f80970434cd6524ae676b277d024b87dd93ecdd3f53bf470d61730dc6cb80" 80 | dependencies = [ 81 | "proc-macro2", 82 | "quote", 83 | "syn", 84 | ] 85 | 86 | [[package]] 87 | name = "borsh-schema-derive-internal" 88 | version = "0.6.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "b3b93230d3769ea99ac75a8a7fee2a229defbc56fe8816c9cde8ed78c848aa33" 91 | dependencies = [ 92 | "proc-macro2", 93 | "quote", 94 | "syn", 95 | ] 96 | 97 | [[package]] 98 | name = "bs58" 99 | version = "0.3.1" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" 102 | 103 | [[package]] 104 | name = "byte-tools" 105 | version = "0.3.1" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 108 | 109 | [[package]] 110 | name = "byteorder" 111 | version = "1.3.4" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 114 | 115 | [[package]] 116 | name = "cfg-if" 117 | version = "0.1.10" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 120 | 121 | [[package]] 122 | name = "digest" 123 | version = "0.8.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 126 | dependencies = [ 127 | "generic-array", 128 | ] 129 | 130 | [[package]] 131 | name = "fake-simd" 132 | version = "0.1.2" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 135 | 136 | [[package]] 137 | name = "generic-array" 138 | version = "0.12.3" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 141 | dependencies = [ 142 | "typenum", 143 | ] 144 | 145 | [[package]] 146 | name = "indexmap" 147 | version = "1.3.2" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" 150 | dependencies = [ 151 | "autocfg", 152 | ] 153 | 154 | [[package]] 155 | name = "itoa" 156 | version = "0.4.5" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 159 | 160 | [[package]] 161 | name = "keccak" 162 | version = "0.1.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" 165 | 166 | [[package]] 167 | name = "lazy_static" 168 | version = "1.4.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 171 | 172 | [[package]] 173 | name = "libc" 174 | version = "0.2.69" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" 177 | 178 | [[package]] 179 | name = "memchr" 180 | version = "2.3.3" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 183 | 184 | [[package]] 185 | name = "memory_units" 186 | version = "0.4.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" 189 | 190 | [[package]] 191 | name = "near-rpc-error-core" 192 | version = "0.1.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "ffa8dbf8437a28ac40fcb85859ab0d0b8385013935b000c7a51ae79631dd74d9" 195 | dependencies = [ 196 | "proc-macro2", 197 | "quote", 198 | "serde", 199 | "serde_json", 200 | "syn", 201 | ] 202 | 203 | [[package]] 204 | name = "near-rpc-error-macro" 205 | version = "0.1.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "0c6111d713e90c7c551dee937f4a06cb9ea2672243455a4454cc7566387ba2d9" 208 | dependencies = [ 209 | "near-rpc-error-core", 210 | "proc-macro2", 211 | "quote", 212 | "serde", 213 | "serde_json", 214 | "syn", 215 | ] 216 | 217 | [[package]] 218 | name = "near-runtime-fees" 219 | version = "0.8.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "4324fa778de675b23edb4da36eac17a05ef7fc40f0a6d36e83cc6040e56fdb30" 222 | dependencies = [ 223 | "serde", 224 | ] 225 | 226 | [[package]] 227 | name = "near-sdk" 228 | version = "0.9.2" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "fdd218ef5ff9acf4ed8375c14aac570589ec837a05d61b60a2e3d212d4358a90" 231 | dependencies = [ 232 | "base64", 233 | "borsh", 234 | "bs58", 235 | "near-runtime-fees", 236 | "near-sdk-macros", 237 | "near-vm-logic", 238 | "serde", 239 | ] 240 | 241 | [[package]] 242 | name = "near-sdk-core" 243 | version = "0.9.2" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "88f97b569352978bc6473f928459ce6f3d040a465b8d087097c6cd038e9dc65d" 246 | dependencies = [ 247 | "Inflector", 248 | "proc-macro2", 249 | "quote", 250 | "syn", 251 | ] 252 | 253 | [[package]] 254 | name = "near-sdk-macros" 255 | version = "0.9.2" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "52b152b7e4821601ae363aa088a9f0b76b120c666b5b6a07aa395fa9a1279a49" 258 | dependencies = [ 259 | "near-sdk-core", 260 | "proc-macro2", 261 | "quote", 262 | "syn", 263 | ] 264 | 265 | [[package]] 266 | name = "near-vm-errors" 267 | version = "0.8.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "464d6d36cf4840bed8ef57a4f043ce1da471b7021171d5470bd9f3112611fe9a" 270 | dependencies = [ 271 | "borsh", 272 | "near-rpc-error-macro", 273 | "serde", 274 | ] 275 | 276 | [[package]] 277 | name = "near-vm-logic" 278 | version = "0.8.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "dd3b537fe6c7b0eada25747c6bd3a3de26f572f5ee84c72fda054f715b83e281" 281 | dependencies = [ 282 | "base64", 283 | "bs58", 284 | "byteorder", 285 | "near-runtime-fees", 286 | "near-vm-errors", 287 | "serde", 288 | "sha2", 289 | "sha3", 290 | ] 291 | 292 | [[package]] 293 | name = "opaque-debug" 294 | version = "0.2.3" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 297 | 298 | [[package]] 299 | name = "proc-macro2" 300 | version = "1.0.10" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" 303 | dependencies = [ 304 | "unicode-xid", 305 | ] 306 | 307 | [[package]] 308 | name = "quote" 309 | version = "1.0.4" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "4c1f4b0efa5fc5e8ceb705136bfee52cfdb6a4e3509f770b478cd6ed434232a7" 312 | dependencies = [ 313 | "proc-macro2", 314 | ] 315 | 316 | [[package]] 317 | name = "regex" 318 | version = "1.3.7" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" 321 | dependencies = [ 322 | "aho-corasick", 323 | "memchr", 324 | "regex-syntax", 325 | "thread_local", 326 | ] 327 | 328 | [[package]] 329 | name = "regex-syntax" 330 | version = "0.6.17" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" 333 | 334 | [[package]] 335 | name = "ryu" 336 | version = "1.0.4" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" 339 | 340 | [[package]] 341 | name = "serde" 342 | version = "1.0.106" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" 345 | dependencies = [ 346 | "serde_derive", 347 | ] 348 | 349 | [[package]] 350 | name = "serde_derive" 351 | version = "1.0.106" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" 354 | dependencies = [ 355 | "proc-macro2", 356 | "quote", 357 | "syn", 358 | ] 359 | 360 | [[package]] 361 | name = "serde_json" 362 | version = "1.0.52" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd" 365 | dependencies = [ 366 | "indexmap", 367 | "itoa", 368 | "ryu", 369 | "serde", 370 | ] 371 | 372 | [[package]] 373 | name = "sha2" 374 | version = "0.8.1" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0" 377 | dependencies = [ 378 | "block-buffer", 379 | "digest", 380 | "fake-simd", 381 | "opaque-debug", 382 | ] 383 | 384 | [[package]] 385 | name = "sha3" 386 | version = "0.8.2" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" 389 | dependencies = [ 390 | "block-buffer", 391 | "byte-tools", 392 | "digest", 393 | "keccak", 394 | "opaque-debug", 395 | ] 396 | 397 | [[package]] 398 | name = "syn" 399 | version = "1.0.18" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213" 402 | dependencies = [ 403 | "proc-macro2", 404 | "quote", 405 | "unicode-xid", 406 | ] 407 | 408 | [[package]] 409 | name = "thread_local" 410 | version = "1.0.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 413 | dependencies = [ 414 | "lazy_static", 415 | ] 416 | 417 | [[package]] 418 | name = "transfer-faucet" 419 | version = "0.1.0" 420 | dependencies = [ 421 | "borsh", 422 | "bs58", 423 | "near-sdk", 424 | "serde", 425 | "serde_json", 426 | "wee_alloc", 427 | ] 428 | 429 | [[package]] 430 | name = "typenum" 431 | version = "1.12.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 434 | 435 | [[package]] 436 | name = "unicode-xid" 437 | version = "0.2.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 440 | 441 | [[package]] 442 | name = "wee_alloc" 443 | version = "0.4.5" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" 446 | dependencies = [ 447 | "cfg-if", 448 | "libc", 449 | "memory_units", 450 | "winapi", 451 | ] 452 | 453 | [[package]] 454 | name = "winapi" 455 | version = "0.3.8" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 458 | dependencies = [ 459 | "winapi-i686-pc-windows-gnu", 460 | "winapi-x86_64-pc-windows-gnu", 461 | ] 462 | 463 | [[package]] 464 | name = "winapi-i686-pc-windows-gnu" 465 | version = "0.4.0" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 468 | 469 | [[package]] 470 | name = "winapi-x86_64-pc-windows-gnu" 471 | version = "0.4.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 474 | -------------------------------------------------------------------------------- /contract-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "transfer-faucet" 3 | version = "0.1.0" 4 | authors = ["Near Inc "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | serde = { version = "*", features = ["derive"] } 12 | serde_json = "*" 13 | near-sdk = "0.9.2" 14 | borsh = "*" 15 | wee_alloc = { version = "0.4.5", default-features = false, features = [] } 16 | bs58 = "0.3.0" 17 | 18 | [profile.release] 19 | codegen-units = 1 20 | # Tell `rustc` to optimize for small code size. 21 | opt-level = "z" 22 | lto = true 23 | debug = false 24 | panic = "abort" 25 | # Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801 26 | overflow-checks = true 27 | 28 | [workspace] 29 | members = [] 30 | -------------------------------------------------------------------------------- /contract-rs/README.md: -------------------------------------------------------------------------------- 1 | # Account faucet with Proof of Work 2 | 3 | A Faucet contract that creates and funds accounts if the caller provides basic proof of work 4 | to avoid sybil attacks and draining balance too fast. 5 | 6 | The new account always receives 1/1000 of the remaining balance. 7 | 8 | Proof of Work works the following way: 9 | 10 | You need to compute a u64 salt (nonce) for a given account and a given public key in such a way 11 | that the `sha256(account_id + ':' + public_key + ':' + salt)` has more leading zero bits than 12 | the required `min_difficulty`. 13 | 14 | ## Testing 15 | To test run: 16 | ```bash 17 | cargo test 18 | ``` 19 | -------------------------------------------------------------------------------- /contract-rs/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | RUSTFLAGS='-C link-arg=-s' cargo build --target wasm32-unknown-unknown --release 5 | cp target/wasm32-unknown-unknown/release/transfer_faucet.wasm ./res/ 6 | 7 | -------------------------------------------------------------------------------- /contract-rs/res/transfer_faucet.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deprecated-near/token-printer/b90b3a09b9d4cef33a1eae64af7293c185dc8997/contract-rs/res/transfer_faucet.wasm -------------------------------------------------------------------------------- /contract-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use near_sdk::collections::Set; 3 | use near_sdk::json_types::{Base58PublicKey, U128}; 4 | use near_sdk::{env, near_bindgen, Promise}; 5 | 6 | #[global_allocator] 7 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 8 | 9 | pub type AccountId = String; 10 | pub type Salt = u64; 11 | 12 | /// A Transfer PoW Faucet contract that allows to request token transfer towards a given account. 13 | /// It uses basic proof of work to avoid sybil attacks. 14 | /// The new account always receives selected amount of tokens. 15 | /// Proof of Work works the following way: 16 | /// You need to compute a u64 salt (nonce) for a given account in such a way 17 | /// that the `sha256(account_id + ':' + salt)` has more leading zero bits than 18 | /// the required `min_difficulty`. The hash has to be unique in order to receive transfer. 19 | /// One account can request multiple transfers. 20 | #[near_bindgen] 21 | #[derive(BorshDeserialize, BorshSerialize)] 22 | pub struct TransferFaucet { 23 | /// Transfer amount 24 | pub transfer_amount: u128, 25 | /// Number of leading zeros in binary representation for a hash 26 | pub min_difficulty: u32, 27 | /// Created accounts 28 | pub existing_hashes: Set>, 29 | } 30 | 31 | impl Default for TransferFaucet { 32 | fn default() -> Self { 33 | panic!("Faucet is not initialized yet") 34 | } 35 | } 36 | 37 | /// Returns the number of leading zero bits for a given slice of bits. 38 | fn num_leading_zeros(v: &[u8]) -> u32 { 39 | let mut res = 0; 40 | for z in v.iter().map(|b| b.leading_zeros()) { 41 | res += z; 42 | if z < 8 { 43 | break; 44 | } 45 | } 46 | res 47 | } 48 | 49 | fn assert_self() { 50 | assert_eq!( 51 | env::current_account_id(), 52 | env::predecessor_account_id(), 53 | "Can only be called by owner" 54 | ); 55 | } 56 | 57 | #[near_bindgen] 58 | impl TransferFaucet { 59 | #[init] 60 | pub fn new(transfer_amount: U128, min_difficulty: u32) -> Self { 61 | assert!(env::state_read::().is_none(), "Already initialized"); 62 | Self { 63 | transfer_amount: transfer_amount.into(), 64 | min_difficulty, 65 | existing_hashes: Set::new(b"h".to_vec()), 66 | } 67 | } 68 | 69 | pub fn get_transfer_amount(&self) -> U128 { 70 | self.transfer_amount.into() 71 | } 72 | 73 | pub fn get_min_difficulty(&self) -> u32 { 74 | self.min_difficulty 75 | } 76 | 77 | pub fn get_num_transfers(&self) -> u64 { 78 | self.existing_hashes.len() 79 | } 80 | 81 | pub fn request_transfer(&mut self, account_id: AccountId, salt: Salt) -> Promise { 82 | // Checking proof of work 83 | // Constructing a message for checking 84 | let mut message = account_id.as_bytes().to_vec(); 85 | message.push(b':'); 86 | message.extend_from_slice(&salt.to_le_bytes()); 87 | // Computing hash of the message 88 | let hash = env::sha256(&message); 89 | // Checking that the resulting hash has enough leading zeros. 90 | assert!( 91 | num_leading_zeros(&hash) >= self.min_difficulty, 92 | "The proof is work is too weak" 93 | ); 94 | 95 | // Checking that the given hash is not used yet and remembering it. 96 | assert!( 97 | self.existing_hashes.insert(&hash), 98 | "The given hash is already used for transfer" 99 | ); 100 | 101 | // Creating a transfer. It still can fail (e.g. account doesn't exists or the name is invalid), 102 | // but this contract will get the refund back. 103 | Promise::new(account_id) 104 | .transfer(self.transfer_amount) 105 | .into() 106 | } 107 | 108 | // Owner's methods. Can only be called by the owner 109 | 110 | pub fn set_min_difficulty(&mut self, min_difficulty: u32) { 111 | assert_self(); 112 | self.min_difficulty = min_difficulty; 113 | } 114 | 115 | pub fn set_transfer_amount(&mut self, transfer_amount: U128) { 116 | assert_self(); 117 | self.transfer_amount = transfer_amount.into(); 118 | } 119 | 120 | pub fn add_access_key(&mut self, public_key: Base58PublicKey) -> Promise { 121 | assert_self(); 122 | Promise::new(env::current_account_id()) 123 | .add_access_key( 124 | public_key.into(), 125 | 0, 126 | env::current_account_id(), 127 | b"request_transfer".to_vec(), 128 | ) 129 | .into() 130 | } 131 | } 132 | 133 | #[cfg(not(target_arch = "wasm32"))] 134 | #[cfg(test)] 135 | mod tests { 136 | use near_sdk::{testing_env, MockedBlockchain, VMContext}; 137 | use std::convert::TryFrom; 138 | use std::panic; 139 | 140 | use super::*; 141 | 142 | fn catch_unwind_silent R + panic::UnwindSafe, R>( 143 | f: F, 144 | ) -> std::thread::Result { 145 | let prev_hook = panic::take_hook(); 146 | panic::set_hook(Box::new(|_| {})); 147 | let result = panic::catch_unwind(f); 148 | panic::set_hook(prev_hook); 149 | result 150 | } 151 | 152 | fn get_context() -> VMContext { 153 | VMContext { 154 | current_account_id: "alice".to_string(), 155 | signer_account_id: "bob".to_string(), 156 | signer_account_pk: vec![0, 1, 2], 157 | predecessor_account_id: "bob".to_string(), 158 | input: vec![], 159 | block_index: 0, 160 | block_timestamp: 0, 161 | account_balance: 10u128.pow(30), 162 | account_locked_balance: 0, 163 | storage_usage: 100, 164 | attached_deposit: 0, 165 | prepaid_gas: 10u64.pow(15), 166 | random_seed: vec![0, 1, 2], 167 | is_view: false, 168 | output_data_receivers: vec![], 169 | epoch_height: 0, 170 | } 171 | } 172 | 173 | #[test] 174 | fn test_new() { 175 | let context = get_context(); 176 | testing_env!(context); 177 | let transfer_amount = 100 * 10u128.pow(24); 178 | let min_difficulty = 5; 179 | let contract = TransferFaucet::new(transfer_amount.into(), min_difficulty); 180 | assert_eq!(contract.get_min_difficulty(), min_difficulty); 181 | assert_eq!(contract.get_transfer_amount().0, transfer_amount); 182 | assert_eq!(contract.get_num_transfers(), 0); 183 | } 184 | 185 | #[test] 186 | fn test_request_transfer_ok() { 187 | let context = get_context(); 188 | testing_env!(context.clone()); 189 | let transfer_amount = 100 * 10u128.pow(24); 190 | let min_difficulty = 5; 191 | let account_id = "test.alice"; 192 | let mut contract = TransferFaucet::new(transfer_amount.into(), min_difficulty); 193 | let mut salt: u64 = 0; 194 | loop { 195 | // To avoid draining all gas 196 | testing_env!(context.clone()); 197 | let mut message = account_id.as_bytes().to_vec(); 198 | message.push(b':'); 199 | message.extend_from_slice(&salt.to_le_bytes()); 200 | // Computing hash of the message 201 | let hash = env::sha256(&message); 202 | // Checking that the resulting hash has enough leading zeros. 203 | if num_leading_zeros(&hash) >= min_difficulty { 204 | break; 205 | } 206 | salt += 1; 207 | } 208 | println!("Salt is {}", salt); 209 | contract.request_transfer(account_id.to_string(), salt); 210 | assert_eq!(contract.get_num_transfers(), 1); 211 | } 212 | 213 | #[test] 214 | fn test_fail_default() { 215 | let context = get_context(); 216 | testing_env!(context); 217 | catch_unwind_silent(|| { 218 | TransferFaucet::default(); 219 | }) 220 | .unwrap_err(); 221 | } 222 | 223 | #[test] 224 | fn test_fail_request_transfer_already_used() { 225 | let context = get_context(); 226 | testing_env!(context); 227 | let transfer_amount = 100 * 10u128.pow(24); 228 | let min_difficulty = 5; 229 | let mut contract = TransferFaucet::new(transfer_amount.into(), min_difficulty); 230 | let account_id = "test.alice"; 231 | let salt = 58; 232 | contract.request_transfer(account_id.to_string(), salt); 233 | catch_unwind_silent(move || { 234 | contract.request_transfer(account_id.to_string(), salt); 235 | }) 236 | .unwrap_err(); 237 | } 238 | 239 | #[test] 240 | fn test_num_leading_zeros() { 241 | assert_eq!(num_leading_zeros(&[0u8; 4]), 32); 242 | assert_eq!(num_leading_zeros(&[255u8; 4]), 0); 243 | assert_eq!(num_leading_zeros(&[254u8; 4]), 0); 244 | assert_eq!(num_leading_zeros(&[]), 0); 245 | assert_eq!(num_leading_zeros(&[127u8]), 1); 246 | assert_eq!(num_leading_zeros(&[0u8; 32]), 256); 247 | assert_eq!(num_leading_zeros(&[1u8; 4]), 7); 248 | assert_eq!(num_leading_zeros(&[0u8, 0u8, 255u8 >> 3]), 19); 249 | assert_eq!(num_leading_zeros(&[0u8, 0u8, 255u8 >> 3, 0u8]), 19); 250 | } 251 | 252 | #[test] 253 | fn test_add_access_key() { 254 | let mut context = get_context(); 255 | context.predecessor_account_id = "alice".to_string(); 256 | testing_env!(context); 257 | let transfer_amount = 100 * 10u128.pow(24); 258 | let min_difficulty = 5; 259 | let mut contract = TransferFaucet::new(transfer_amount.into(), min_difficulty); 260 | contract.add_access_key( 261 | Base58PublicKey::try_from("ed25519:CFsEoaPizaj2uPP5StphygRTVugh1anqG8JpiGzpFHs") 262 | .unwrap(), 263 | ); 264 | } 265 | 266 | #[test] 267 | fn test_bad_public_key() { 268 | let mut context = get_context(); 269 | context.predecessor_account_id = "alice".to_string(); 270 | testing_env!(context); 271 | let transfer_amount = 100 * 10u128.pow(24); 272 | let min_difficulty = 5; 273 | let mut contract = TransferFaucet::new(transfer_amount.into(), min_difficulty); 274 | catch_unwind_silent(move || { 275 | contract.add_access_key( 276 | Base58PublicKey::try_from("ed25519:CFsEoaPTVugh1anqG8JpiGzpFHs").unwrap(), 277 | ); 278 | }) 279 | .unwrap_err(); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn deploy` 16 | 17 | Deploys app to github pages.
18 | 19 | ### `yarn test` 20 | 21 | Launches the test runner in the interactive watch mode.
22 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 23 | 24 | ### `yarn build` 25 | 26 | Builds the app for production to the `build` folder.
27 | It correctly bundles React in production mode and optimizes the build for the best performance. 28 | 29 | The build is minified and the filenames include the hashes.
30 | Your app is ready to be deployed! 31 | 32 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 33 | 34 | ### `yarn eject` 35 | 36 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 37 | 38 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 39 | 40 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 41 | 42 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 43 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "token-printer", 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^5.11.4", 8 | "@testing-library/react": "^10.4.4", 9 | "@testing-library/user-event": "^12.1.5", 10 | "bn.js": "^5.1.3", 11 | "near-api-js": "^0.43.1", 12 | "react": "^16.13.1", 13 | "react-dom": "^16.13.1", 14 | "react-scripts": "3.4.3" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject", 21 | "deploy": "yarn build && gh-pages -d build", 22 | "deploy:contract":"cd .. && cd contract-rs && cd target && cd wasm32-unknown-unknown && near dev-deploy release/transfer_faucet.wasm " 23 | }, 24 | "eslintConfig": { 25 | "extends": "react-app" 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | }, 39 | "devDependencies": { 40 | "gh-pages": "^3.1.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deprecated-near/token-printer/b90b3a09b9d4cef33a1eae64af7293c185dc8997/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 18 | 19 | 28 | Token Printer 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deprecated-near/token-printer/b90b3a09b9d4cef33a1eae64af7293c185dc8997/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deprecated-near/token-printer/b90b3a09b9d4cef33a1eae64af7293c185dc8997/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import BN from "bn.js"; 3 | import * as nearAPI from "near-api-js"; 4 | 5 | const FaucetPrivateKey = 6 | "ed25519:4a5T9u2ek3xNwP74EWZ8n94RBpWzj8ofgEzeNkLv2XqypomDyRpU2ENGrf9qBkuDCy9b8dat7TGiK4h649yYAd2j"; 7 | const FaucetName = "token-printer"; 8 | const MinAccountIdLen = 2; 9 | const MaxAccountIdLen = 64; 10 | const ValidAccountRe = /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/; 11 | const OneNear = new BN("1000000000000000000000000"); 12 | 13 | const fromYocto = (a) => a / OneNear; 14 | const brrr = (n) => "B" + "R".repeat(n); 15 | 16 | class App extends React.Component { 17 | constructor(props) { 18 | super(props); 19 | 20 | this.state = { 21 | connected: false, 22 | signedIn: false, 23 | accountId: "", 24 | requesting: false, 25 | accountLoading: false, 26 | accountExists: false, 27 | computingProofOfWork: false, 28 | numTransfers: 0, 29 | tokenPrinterBalance: "0", 30 | }; 31 | 32 | this.initNear().then(() => { 33 | this.setState({ 34 | connected: true, 35 | }); 36 | }); 37 | } 38 | 39 | async initFaucet() { 40 | let key = await this._keyStore.getKey( 41 | this._nearConfig.networkId, 42 | FaucetName 43 | ); 44 | if (!key) { 45 | const keyPair = nearAPI.KeyPair.fromString(FaucetPrivateKey); 46 | await this._keyStore.setKey( 47 | this._nearConfig.networkId, 48 | FaucetName, 49 | keyPair 50 | ); 51 | } 52 | const account = new nearAPI.Account(this._near.connection, FaucetName); 53 | this._faucetContract = new nearAPI.Contract(account, FaucetName, { 54 | viewMethods: [ 55 | "get_min_difficulty", 56 | "get_transfer_amount", 57 | "get_num_transfers", 58 | ], 59 | changeMethods: ["request_transfer"], 60 | sender: FaucetName, 61 | }); 62 | this._transferAmount = new BN( 63 | await this._faucetContract.get_transfer_amount() 64 | ); 65 | this._minDifficulty = await this._faucetContract.get_min_difficulty(); 66 | this.setState({ 67 | numTransfers: await this._faucetContract.get_num_transfers(), 68 | }); 69 | } 70 | 71 | async initNear() { 72 | const nearConfig = { 73 | networkId: "testnet", 74 | nodeUrl: "https://rpc.testnet.near.org", 75 | contractName: FaucetName, 76 | walletUrl: "https://wallet.testnet.near.org", 77 | }; 78 | const keyStore = new nearAPI.keyStores.BrowserLocalStorageKeyStore(); 79 | const near = await nearAPI.connect( 80 | Object.assign({ deps: { keyStore } }, nearConfig) 81 | ); 82 | this._keyStore = keyStore; 83 | this._nearConfig = nearConfig; 84 | this._near = near; 85 | 86 | const accountInfo = await near.account("token-printer"); 87 | const accountBalances = await accountInfo.getAccountBalance(); 88 | const formattedAccountBalance = nearAPI.utils.format.formatNearAmount( 89 | accountBalances.available 90 | ); 91 | 92 | this.setState({ 93 | tokenPrinterBalance: formattedAccountBalance, 94 | }); 95 | 96 | await this.initFaucet(); 97 | } 98 | 99 | handleChange(key, value) { 100 | const stateChange = { 101 | [key]: value, 102 | }; 103 | if (key === "accountId") { 104 | value = value.toLowerCase().replace(/[^a-z0-9\-_.]/, ""); 105 | stateChange[key] = value; 106 | stateChange.accountExists = false; 107 | if (this.isValidAccount(value)) { 108 | stateChange.accountLoading = true; 109 | this._near.connection.provider 110 | .query(`account/${value}`, "") 111 | .then((_a) => { 112 | if (this.state.accountId === value) { 113 | this.setState({ 114 | accountLoading: false, 115 | accountExists: true, 116 | }); 117 | } 118 | }) 119 | .catch((e) => { 120 | if (this.state.accountId === value) { 121 | this.setState({ 122 | accountLoading: false, 123 | accountExists: false, 124 | }); 125 | } 126 | }); 127 | } 128 | } 129 | this.setState(stateChange); 130 | } 131 | 132 | isValidAccount(accountId) { 133 | return ( 134 | accountId.length >= MinAccountIdLen && 135 | accountId.length <= MaxAccountIdLen && 136 | accountId.match(ValidAccountRe) 137 | ); 138 | } 139 | 140 | accountClass() { 141 | if (!this.state.accountId || this.state.accountLoading) { 142 | return "form-control form-control-large"; 143 | } else if ( 144 | this.state.accountExists && 145 | this.isValidAccount(this.state.accountId) 146 | ) { 147 | return "form-control form-control-large is-valid"; 148 | } else { 149 | return "form-control form-control-large is-invalid"; 150 | } 151 | } 152 | 153 | async computeProofOfWork(accountId, initialSalt) { 154 | let msg = [...new TextEncoder("utf-8").encode(accountId + ":")]; 155 | // salt 156 | let t = initialSalt; 157 | for (let i = 0; i < 8; ++i) { 158 | msg.push(t & 255); 159 | t = Math.floor(t / 256); 160 | } 161 | msg = new Uint8Array(msg); 162 | const len = msg.length; 163 | let bestDifficulty = 0; 164 | for (let salt = initialSalt; ; ++salt) { 165 | // compute hash 166 | const hashBuffer = new Uint8Array( 167 | await crypto.subtle.digest("SHA-256", msg) 168 | ); 169 | // compute number of leading zero bits 170 | let totalNumZeros = 0; 171 | for (let i = 0; i < hashBuffer.length; ++i) { 172 | let numZeros = Math.clz32(hashBuffer[i]) - 24; 173 | totalNumZeros += numZeros; 174 | if (numZeros < 8) { 175 | break; 176 | } 177 | } 178 | // checking difficulty 179 | if (totalNumZeros >= this._minDifficulty) { 180 | this.setState({ 181 | computingProofOfWork: false, 182 | }); 183 | return salt; 184 | } else if (totalNumZeros > bestDifficulty) { 185 | bestDifficulty = totalNumZeros; 186 | this.setState({ 187 | proofOfWorkProgress: Math.trunc( 188 | (bestDifficulty * 100) / this._minDifficulty 189 | ), 190 | proofOfWorkDifficulty: bestDifficulty, 191 | proofOfWorkSalt: salt - initialSalt, 192 | }); 193 | } else if (salt % 10000 === 0) { 194 | this.setState({ 195 | proofOfWorkSalt: salt - initialSalt, 196 | }); 197 | } 198 | // incrementing salt 199 | for (let i = len - 8; i < len; ++i) { 200 | if (msg[i] === 255) { 201 | msg[i] = 0; 202 | } else { 203 | ++msg[i]; 204 | break; 205 | } 206 | } 207 | } 208 | } 209 | 210 | async requestTransfer() { 211 | this.setState({ 212 | requesting: true, 213 | computingProofOfWork: true, 214 | proofOfWorkProgress: 0, 215 | proofOfWorkDifficulty: 0, 216 | proofOfWorkSalt: 0, 217 | }); 218 | const accountId = this.state.accountId; 219 | const salt = await this.computeProofOfWork(accountId, new Date().getTime()); 220 | await this._faucetContract.request_transfer({ 221 | account_id: accountId, 222 | salt, 223 | }); 224 | this.setState({ 225 | requesting: false, 226 | numTransfers: await this._faucetContract.get_num_transfers(), 227 | }); 228 | } 229 | 230 | render() { 231 | const content = !this.state.connected ? ( 232 |
233 | Connecting...{" "} 234 | 239 |
240 | ) : ( 241 |
242 |
243 | 250 |
251 |
252 |
{"@"}
253 |
254 | this.handleChange("accountId", e.target.value)} 260 | disabled={this.state.requesting} 261 | /> 262 |
263 |
264 | {this.state.accountId && 265 | !this.state.accountLoading && 266 | !this.state.accountExists && ( 267 |
268 | Account {"@" + this.state.accountId} doesn't exist! You may want 269 | to try create it with{" "} 270 | 271 | PoW Faucet 272 | 273 |
274 | )} 275 |
276 | 295 |
296 | {this.state.requesting && ( 297 |
298 | {this.state.computingProofOfWork ? ( 299 |
300 | Token printer goes {brrr(this.state.proofOfWorkSalt / 10000)}. 301 |
302 |
310 | {brrr(this.state.proofOfWorkDifficulty)} out of{" "} 311 | {brrr(this._minDifficulty)} 312 |
313 |
314 |
315 | BRRRRR 319 |
320 |
321 | ) : ( 322 |
323 | Printing is Done! Delivering. 324 |
325 |
326 | Delivering 330 |
331 |
332 | )} 333 |
334 | )} 335 |
336 | ); 337 | return ( 338 |
339 |
340 |

Token Printer (testnet)

341 |
342 | Yo, Cash 346 |
347 |

348 | There were{" "} 349 | 350 | {this.state.numTransfers} accounts 351 | {" "} 352 | funded and total{" "} 353 | 354 | {fromYocto(this.state.numTransfers * this._transferAmount)} Ⓝ 355 | {" "} 356 | tokens were printed. 357 |

358 |
359 | {parseInt(this.state.tokenPrinterBalance) < 100 ? ( 360 |
361 | The Token Printer has{" "} 362 | 363 | {this.state.tokenPrinterBalance}Ⓝ 364 | {" "} 365 | remaining. The balance is too low, please let a NEAR Dev Rel Member 366 | know in the Discord "dev-support" channel or the Telegram "NEAR Dev" 367 | channel. 368 |
369 | ) : ( 370 |
371 | The Token Printer has{" "} 372 | 373 | {this.state.tokenPrinterBalance}Ⓝ 374 | {" "} 375 | remaining 376 |
377 | )} 378 |
379 | {content} 380 |
381 | ); 382 | } 383 | } 384 | 385 | export default App; 386 | -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders header', () => { 6 | const { getByText } = render(); 7 | const header = getByText(/Proof of Work Faucet/i); 8 | expect(header).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | --------------------------------------------------------------------------------