├── .dockerignore ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── config ├── example_user_config_main.ini └── example_user_config_nodes.ini ├── demos └── api_demo.py ├── doc ├── CHANGELOG.md ├── DESIGN_AND_FEATURES.md ├── IMG_API_SERVER_DESIGN.drawio ├── IMG_API_SERVER_DESIGN_5x.png ├── IMG_POLKADOT_API_SERVER.png ├── INSTALL_AND_RUN.md ├── INSTALL_DOCKER.md └── INSTALL_PYTHON.md ├── package-lock.json ├── package.json ├── run_api.sh ├── run_setup.py ├── setup ├── setup_user_config_main.py ├── setup_user_config_nodes.py └── utils │ ├── config_parsers │ └── user.py │ └── user_input.py ├── src ├── interface │ ├── substrate_derive.js │ ├── substrate_query.js │ └── substrate_rpc.js ├── server.js └── utils │ └── timeout.js └── test └── report_system_testing.xlsx /.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | node_modules 3 | config 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | __pycache__/ 3 | 4 | node_modules/ 5 | 6 | config/user_config_main.ini 7 | config/user_config_nodes.ini 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behaviour that contributes to creating a positive environment 10 | include: 11 | 12 | * Using welcoming and inclusive language 13 | * Being respectful of differing viewpoints and experiences 14 | * Gracefully accepting constructive criticism 15 | * Focusing on what is best for the community 16 | * Showing empathy towards other community members 17 | 18 | Examples of unacceptable behaviour by participants include: 19 | 20 | * The use of sexualised language or imagery and unwelcome sexual attention or 21 | advances 22 | * Trolling, insulting/derogatory comments, and personal or political attacks 23 | * Public or private harassment 24 | * Publishing others' private information, such as a physical or electronic 25 | address, without explicit permission 26 | * Other conduct which could reasonably be considered inappropriate in a 27 | professional setting 28 | 29 | ## Our Responsibilities 30 | 31 | Project maintainers are responsible for clarifying the standards of acceptable behaviour and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behaviour. 32 | 33 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviours that they deem inappropriate, threatening, offensive, or harmful. 34 | 35 | ## Scope 36 | 37 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 38 | 39 | ## Enforcement 40 | 41 | Instances of abusive, harassing, or otherwise unacceptable behaviour may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 42 | 43 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 44 | 45 | ## Attribution 46 | 47 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 48 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 49 | 50 | [homepage]: https://www.contributor-covenant.org 51 | 52 | --- 53 | [Back to contributing guidelines page](CONTRIBUTING.md) -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | We welcome any kind of contribution to our software, from simple comment or question to a full fledged [pull request](https://help.github.com/articles/about-pull-requests/). Please read and follow our [Code of Conduct](CODE_OF_CONDUCT.md). 4 | 5 | A contribution can be one of the following cases: 6 | 7 | 1. you have a question; 8 | 1. you think you may have found a bug (including unexpected behavior); 9 | 1. you want to make some kind of change to the code base (e.g. to fix a bug, to add a new feature, to update documentation). 10 | 11 | The sections below outline the steps in each case. 12 | 13 | ## You have a question 14 | 15 | 1. use the search functionality [here](https://github.com/SimplyVC/polkadot_api_server/issues) to see if someone already filed the same issue; 16 | 1. if your issue search did not yield any relevant results, make a new issue; 17 | 1. apply the "Question" label; apply other labels when relevant. 18 | 19 | ## You think you may have found a bug 20 | 21 | 1. use the search functionality [here](https://github.com/SimplyVC/polkadot_api_server/issues) to see if someone already filed the same issue; 22 | 1. if your issue search did not yield any relevant results, make a new issue, making sure to provide enough information to the rest of the community to understand the cause and context of the problem. Depending on the issue, you may want to include: 23 | - the [SHA hashcode](https://help.github.com/articles/autolinked-references-and-urls/#commit-shas) of the commit that is causing your problem; 24 | - some identifying information (name and version number) for dependencies you're using; 25 | - information about the operating system; 26 | 1. apply relevant labels to the newly created issue. 27 | 28 | ## You want to make some kind of change to the code base 29 | 30 | 1. (**important**) announce your plan to the rest of the community _before you start working_. This announcement should be in the form of a (new) issue; 31 | 1. (**important**) wait until some kind of consensus is reached about your idea being a good idea; 32 | 1. if needed, fork the repository to your own Github profile and create your own feature branch off of the latest master commit. While working on your feature branch, make sure to stay up to date with the master branch by pulling in changes, possibly from the 'upstream' repository (follow the instructions [here](https://help.github.com/articles/configuring-a-remote-for-a-fork/) and [here](https://help.github.com/articles/syncing-a-fork/)); 33 | 1. make sure the existing endpoints still work correctly by running `pipenv run python demos/api_demo.py`; 34 | (If multiple versions of Python are installed, the `python` executable may be `python3.6`, `python3.7`, etc.) 35 | 1. update the demo script (if applicable); 36 | 1. update the test/report_system_testing.xlsx file with tests for the new feature (if applicable); 37 | 1. update or expand the documentation; 38 | 1. [push](http://rogerdudler.github.io/git-guide/) your feature branch to (your fork of) the PANIC alerter repository on GitHub; 39 | 1. create the pull request, e.g. following the instructions [here](https://help.github.com/articles/creating-a-pull-request/). 40 | 41 | In case you feel like you've made a valuable contribution, but you don't know how to write or run tests for it, or how to generate the documentation: don't let this discourage you from making the pull request; we can help you! Just go ahead and submit the pull request, but keep in mind that you might be asked to append additional commits to your pull request. 42 | 43 | --- 44 | Contribution guidelines adapted from [python-template](https://github.com/NLeSC/python-template). 45 | 46 | --- 47 | [Back to front page](README.md) -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | # Create app directory 4 | WORKDIR /opt/polkadot_api_server 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | COPY package*.json ./ 9 | 10 | #RUN npm install 11 | RUN npm ci --only=production 12 | 13 | # Bundle app source 14 | COPY . . 15 | WORKDIR ./src 16 | 17 | EXPOSE 3000 18 | 19 | CMD [ "node", "server.js" ] 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Dylan Galea 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [packages] 7 | configparser = "*" 8 | requests = "*" 9 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "35984ec5e721f942ddf83df0a193cb7ef98f13c0e0d9d6f78ba62d6e7bc034d8" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": {}, 8 | "sources": [ 9 | { 10 | "name": "pypi", 11 | "url": "https://pypi.org/simple", 12 | "verify_ssl": true 13 | } 14 | ] 15 | }, 16 | "default": { 17 | "certifi": { 18 | "hashes": [ 19 | "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", 20 | "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" 21 | ], 22 | "version": "==2019.11.28" 23 | }, 24 | "chardet": { 25 | "hashes": [ 26 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 27 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 28 | ], 29 | "version": "==3.0.4" 30 | }, 31 | "configparser": { 32 | "hashes": [ 33 | "sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c", 34 | "sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df" 35 | ], 36 | "index": "pypi", 37 | "version": "==4.0.2" 38 | }, 39 | "idna": { 40 | "hashes": [ 41 | "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", 42 | "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" 43 | ], 44 | "version": "==2.8" 45 | }, 46 | "requests": { 47 | "hashes": [ 48 | "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", 49 | "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" 50 | ], 51 | "index": "pypi", 52 | "version": "==2.22.0" 53 | }, 54 | "urllib3": { 55 | "hashes": [ 56 | "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", 57 | "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" 58 | ], 59 | "version": "==1.25.8" 60 | } 61 | }, 62 | "develop": {} 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polkadot API Server 2 | 3 | design 4 | 5 | The Polkadot API Server is a wrap-around of the [polkadot-js/api](https://polkadot.js.org/api/). This makes it easier to use the polkadot-js/api with any programming language in order to query data from Polkadot nodes. In addition to this, a number of custom defined calls were also implemented in the API Server. For example, one can query directly the amount of tokens a validator has been slashed at any block height. 6 | 7 | The API Server was specifically built as a way for [PANIC](https://github.com/SimplyVC/panic_polkadot) to be able to retrieve data from the Polkadot nodes that it will monitor. As a result, not all functions from the polkadot-js/api were included in the API Server. 8 | If you would like any endpoint specified in the [RPC docs](https://polkadot.js.org/docs/substrate/rpc) or the [query docs](https://polkadot.js.org/docs/substrate/storage) to be implemented, kindly [open an issue](https://github.com/SimplyVC/polkadot_api_server/issues) and we will consider adding it in a future release. You might also want to have a look at our [contribution guidelines](CONTRIBUTING.md), especially if you want to try adding it yourself. 9 | 10 | ## Design and Features 11 | 12 | If you want to dive into the design and feature set of the API Server [click here](doc/DESIGN_AND_FEATURES.md). 13 | 14 | ## Ready, Set, Query! 15 | 16 | If you are ready to try out the API Server on your Polkadot nodes, setup and run the API Server using [this](doc/INSTALL_AND_RUN.md) guide. 17 | -------------------------------------------------------------------------------- /config/example_user_config_main.ini: -------------------------------------------------------------------------------- 1 | [api_server] 2 | port = 3000 3 | -------------------------------------------------------------------------------- /config/example_user_config_nodes.ini: -------------------------------------------------------------------------------- 1 | [node_1] 2 | node_name = validator_1 3 | ws_url = ws://123.123.123.1:9944 4 | 5 | [node_2] 6 | node_name = node_2 7 | ws_url = ws://123.123.123.2:9944 8 | 9 | [node_3] 10 | node_name = node_3 11 | ws_url = ws://123.123.123.3:9944 12 | 13 | [node_4] 14 | node_name = node_4 15 | ws_url = ws://123.123.123.4:9944 16 | 17 | #[node_5] 18 | #node_name = node_5 19 | #ws_url = ws://123.123.123.5:9944 20 | -------------------------------------------------------------------------------- /demos/api_demo.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | # defining the api-endpoint 4 | API_ENDPOINT: str = "http://localhost:3000" 5 | # API_ENDPOINT: str = "http://5.6.7.8:3000" 6 | 7 | websocket: str = 'ws://1.2.3.4:9944' 8 | 9 | # Miscellaneous Endpoints 10 | print('Miscellaneous Endpoints:') 11 | 12 | print('/api/pingApi') 13 | r = requests.get(url=API_ENDPOINT + '/api/pingApi') 14 | print(r.text) 15 | 16 | print('/api/pingNode') 17 | r = requests.get(url=API_ENDPOINT + '/api/pingNode', 18 | params={'websocket': websocket}) 19 | print(r.text) 20 | 21 | print('/api/getConnectionsList') 22 | r = requests.get(url=API_ENDPOINT + '/api/getConnectionsList') 23 | print(r.text) 24 | 25 | print() 26 | 27 | # RPC API 28 | # Chain 29 | print('Chain:') 30 | 31 | print('/api/rpc/chain/getBlockHash') 32 | r = requests.get(url=API_ENDPOINT + '/api/rpc/chain/getBlockHash', 33 | params={'websocket': websocket}) 34 | print(r.text) 35 | r = requests.get(url=API_ENDPOINT + '/api/rpc/chain/getBlockHash', 36 | params={'websocket': websocket, 37 | 'block_number': '36430'}) 38 | print(r.text) 39 | 40 | print('/api/rpc/chain/getFinalizedHead') 41 | r = requests.get(url=API_ENDPOINT + '/api/rpc/chain/getFinalizedHead', 42 | params={'websocket': websocket}) 43 | print(r.text) 44 | 45 | print('/api/rpc/chain/getHeader') 46 | r = requests.get(url=API_ENDPOINT + '/api/rpc/chain/getHeader', 47 | params={'websocket': websocket}) 48 | print(r.text) 49 | r = requests.get(url=API_ENDPOINT + '/api/rpc/chain/getHeader', 50 | params={'websocket': websocket, 51 | 'hash': '0xdd661348a4971e0cf75d89da69de01907e81070cb8099dddc12b611c18371679'}) 52 | print(r.text) 53 | 54 | # RPC 55 | print('RPC:') 56 | 57 | print('/api/rpc/rpc/methods') 58 | r = requests.get(url=API_ENDPOINT + '/api/rpc/rpc/methods', 59 | params={'websocket': websocket}) 60 | print(r.text) 61 | 62 | # System 63 | print('System:') 64 | 65 | print('/api/rpc/system/chain') 66 | r = requests.get(url=API_ENDPOINT + '/api/rpc/system/chain', 67 | params={'websocket': websocket}) 68 | print(r.text) 69 | 70 | print('/api/rpc/system/health') 71 | r = requests.get(url=API_ENDPOINT + '/api/rpc/system/health', 72 | params={'websocket': websocket}) 73 | print(r.text) 74 | 75 | print('/api/rpc/system/networkState') 76 | r = requests.get(url=API_ENDPOINT + '/api/rpc/system/networkState', 77 | params={'websocket': websocket}) 78 | print(r.text) 79 | 80 | print('/api/rpc/system/properties') 81 | r = requests.get(url=API_ENDPOINT + '/api/rpc/system/properties', 82 | params={'websocket': websocket}) 83 | print(r.text) 84 | 85 | print() 86 | 87 | # Query API 88 | # Balances 89 | print('Balances:') 90 | 91 | print('/api/query/balances/totalIssuance') 92 | r = requests.get(url=API_ENDPOINT + '/api/query/balances/totalIssuance', 93 | params={'websocket': websocket}) 94 | print(r.text) 95 | 96 | # Council 97 | print('Council:') 98 | 99 | print('/api/query/council/members') 100 | r = requests.get(url=API_ENDPOINT + '/api/query/council/members', 101 | params={'websocket': websocket}) 102 | print(r.text) 103 | 104 | print('/api/query/council/proposalCount') 105 | r = requests.get(url=API_ENDPOINT + '/api/query/council/proposalCount', 106 | params={'websocket': websocket}) 107 | print(r.text) 108 | 109 | print('/api/query/council/proposalOf') 110 | r = requests.get(url=API_ENDPOINT + '/api/query/council/proposalOf', 111 | params={'websocket': websocket, 112 | 'hash': 'boq'}) 113 | print(r.text) 114 | 115 | print('/api/query/council/proposals') 116 | r = requests.get(url=API_ENDPOINT + '/api/query/council/proposals', 117 | params={'websocket': websocket}) 118 | print(r.text) 119 | 120 | # Democracy 121 | print('Democracy:') 122 | 123 | print('/api/query/democracy/publicPropCount') 124 | r = requests.get(url=API_ENDPOINT + '/api/query/democracy/publicPropCount', 125 | params={'websocket': websocket}) 126 | print(r.text) 127 | 128 | print('/api/query/democracy/referendumCount') 129 | r = requests.get(url=API_ENDPOINT + '/api/query/democracy/referendumCount', 130 | params={'websocket': websocket}) 131 | print(r.text) 132 | 133 | print('/api/query/democracy/referendumInfoOf') 134 | r = requests.get(url=API_ENDPOINT + '/api/query/democracy/referendumInfoOf', 135 | params={'websocket': websocket, 136 | 'referendum_index': '43'}) 137 | print(r.text) 138 | 139 | # ImOnline 140 | print('ImOnline:') 141 | 142 | print('/api/query/imOnline/authoredBlocks') 143 | r = requests.get(url=API_ENDPOINT + '/api/query/imOnline/authoredBlocks', 144 | params={'websocket': websocket, 145 | 'session_index': '3', 146 | 'validator_id': 'DNDBcYD8zzqAoZEtgNzouVp2sVxsvqzD4UdB5WrAUwjqpL8'}) 147 | print(r.text) 148 | 149 | print('/api/query/imOnline/receivedHeartbeats') 150 | r = requests.get(url=API_ENDPOINT + '/api/query/imOnline/receivedHeartbeats', 151 | params={'websocket': websocket, 152 | 'session_index': '3', 153 | 'auth_index': '0'}) 154 | print(r.text) 155 | 156 | # Session 157 | print('Session:') 158 | 159 | print('/api/query/session/currentIndex') 160 | r = requests.get(url=API_ENDPOINT + '/api/query/session/currentIndex', 161 | params={'websocket': websocket}) 162 | print(r.text) 163 | 164 | print('/api/query/session/disabledValidators') 165 | r = requests.get(url=API_ENDPOINT + '/api/query/session/disabledValidators', 166 | params={'websocket': websocket}) 167 | print(r.text) 168 | 169 | print('/api/query/session/validators') 170 | r = requests.get(url=API_ENDPOINT + '/api/query/session/validators', 171 | params={'websocket': websocket}) 172 | print(r.text) 173 | 174 | # Staking 175 | print('Staking:') 176 | 177 | print('/api/query/staking/activeEra') 178 | r = requests.get(url=API_ENDPOINT + '/api/query/staking/activeEra', 179 | params={'websocket': websocket}) 180 | print(r.text) 181 | 182 | print('/api/query/staking/erasRewardPoints') 183 | r = requests.get(url=API_ENDPOINT + '/api/query/staking/erasRewardPoints', 184 | params={'websocket': websocket}) 185 | print(r.text) 186 | r = requests.get(url=API_ENDPOINT + '/api/query/staking/erasRewardPoints', 187 | params={'websocket': websocket, 188 | 'era_index': '630'}) 189 | print(r.text) 190 | 191 | 192 | print('/api/query/staking/erasStakers') 193 | r = requests.get(url=API_ENDPOINT + '/api/query/staking/erasStakers', 194 | params={'websocket': websocket, 195 | 'account_id': 'DNDBcYD8zzqAoZEtgNzouVp2sVxsvqzD4UdB5WrAUwjqpL8'}) 196 | print(r.text) 197 | r = requests.get(url=API_ENDPOINT + '/api/query/staking/erasStakers', 198 | params={'websocket': websocket, 199 | 'account_id': 'DNDBcYD8zzqAoZEtgNzouVp2sVxsvqzD4UdB5WrAUwjqpL8', 200 | 'era_index': '630'}) 201 | print(r.text) 202 | 203 | print('/api/query/staking/erasTotalStake') 204 | r = requests.get(url=API_ENDPOINT + '/api/query/staking/erasTotalStake', 205 | params={'websocket': websocket}) 206 | print(r.text) 207 | r = requests.get(url=API_ENDPOINT + '/api/query/staking/erasTotalStake', 208 | params={'websocket': websocket, 209 | 'era_index': '630'}) 210 | print(r.text) 211 | 212 | print('/api/query/staking/erasValidatorReward') 213 | r = requests.get(url=API_ENDPOINT + '/api/query/staking/erasValidatorReward', 214 | params={'websocket': websocket}) 215 | print(r.text) 216 | r = requests.get(url=API_ENDPOINT + '/api/query/staking/erasValidatorReward', 217 | params={'websocket': websocket, 218 | 'era_index': '840'}) 219 | print(r.text) 220 | 221 | 222 | # System 223 | print('System:') 224 | 225 | print('/api/query/system/events') 226 | r = requests.get(url=API_ENDPOINT + '/api/query/system/events', 227 | params={'websocket': websocket}) 228 | print(r.text) 229 | r = requests.get(url=API_ENDPOINT + '/api/query/system/events', 230 | params={'websocket': websocket, 231 | 'block_hash': '0x1511c16054f1beaa4995cf8c637d6450f1a77acfa40f9f3f51579bba2b92a6c7'}) 232 | print(r.text) 233 | 234 | print() 235 | 236 | # Custom 237 | print('Custom:') 238 | 239 | print('/api/custom/getSlashAmount') 240 | r = requests.get(url=API_ENDPOINT + '/api/custom/getSlashAmount', 241 | params={'websocket': websocket, 242 | 'account_address': 'HsGrsqL4nCBCW2ovc4kKG98c4mFp99BHRFkBSRZW1ETDe3U'}) 243 | print(r.text) 244 | r = requests.get(url=API_ENDPOINT + '/api/custom/getSlashAmount', 245 | params={'websocket': websocket, 246 | 'block_hash': '0x1511c16054f1beaa4995cf8c637d6450f1a77acfa40f9f3f51579bba2b92a6c7', 247 | 'account_address': 'HsGrsqL4nCBCW2ovc4kKG98c4mFp99BHRFkBSRZW1ETDe3U'}) 248 | print(r.text) 249 | 250 | print() 251 | 252 | # Derive 253 | print('Derive:') 254 | 255 | print('/api/derive/staking/validators') 256 | r = requests.get(url=API_ENDPOINT + '/api/derive/staking/validators', 257 | params={'websocket': websocket}) 258 | print(r.text) 259 | 260 | print() 261 | 262 | # Misc 263 | print('Misc:') 264 | r = requests.get(url=API_ENDPOINT + '/api/query/session/validators', 265 | params={'websocket': websocket}) 266 | print(r.text) 267 | 268 | print() 269 | 270 | # Invalid IP 271 | r = requests.get(url=API_ENDPOINT + '/api/query/session/validators', 272 | params={'websocket': 'ws://2.3.4.5:9944'}) 273 | print(r.text) 274 | 275 | # r_text = r.text 276 | # r_json = json.loads(r_text) 277 | # 278 | # print(r.text) 279 | # print(r_json) 280 | # print(r_json['result']) 281 | -------------------------------------------------------------------------------- /doc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Unreleased 4 | 5 | ## 1.29.1 6 | 7 | Released on 23rd July 2021 8 | 9 | * Updated @polkadot/api and @polkadot/api-derive packages to v5.1.1. 10 | * Updated node version in dockerfile to v14. 11 | 12 | ## 1.28.1 13 | 14 | Released on 7th May 2021 15 | 16 | * Updated @polkadot/api and @polkadot/api-derive packages to v4.9.2. 17 | 18 | ## 1.27.1 19 | 20 | Released on 13th April 2021 21 | 22 | * Updated @polkadot/api and @polkadot/api-derive packages to v4.3.1. 23 | * Added `staking/bonded`. This returns the controller account assigned to the stash account with the specified `account_id`. 24 | * Added `staking/payee`. This returns the reward destination address assigned to the stash with the specified `account_id`. 25 | * Added `staking/validators`. This returns the preferences of the validator whose stash is the specified `account_id`. 26 | 27 | ## 1.26.1 28 | 29 | Released on 28th January 2021 30 | 31 | * Added the `staking/unappliedSlashes`. This returns all the unapplied slashes in the specified era, or the active era if an era index is not specified. 32 | * Fixed width of images in the documentation. 33 | * Updated @polkadot/api and @polkadot/api-derive packages to v3.6.4. 34 | 35 | ## 1.25.1 36 | 37 | Released on 15th October 2020 38 | 39 | * Updated @polkadot/api and @polkadot/api-derive packages to v2.2.1. 40 | * Since `provider.isConnected` is now a variable in the polkadot-js/api packages, we updated this in the server code. 41 | 42 | ## 1.24.1 43 | 44 | Released on 12th October 2020 45 | 46 | * Updated @polkadot/api and @polkadot/api-derive packages to v2.1.1. 47 | 48 | ## 1.23.1 49 | 50 | Released on 10th July 2020 51 | 52 | * Included the `--rpc-cors=all` flag in the `INSTALL_AND_RUN.md` guide. Note: All nodes to be added to the API server must run with this flag. 53 | * Updated @polkadot/api and @polkadot/api-derive packages to v1.23.1. 54 | * Code refactoring. 55 | * All endpoints now clear the timers to avoid memory leaks. 56 | * Every endpoint will now return a `Lost connection with node.` error message whenever the WebSocket connection is lost with the querying node. 57 | 58 | ## 1.18.1 59 | 60 | Released on 9th June 2020 61 | 62 | Updated @polkadot/api and @polkadot/api-derive packages to v1.18.1 63 | 64 | ## 1.16.1 65 | 66 | Released on 27th May 2020 67 | 68 | Updated @polkadot/api and @polkadot/api-derive packages to v1.16.1 69 | Updated error logging for API calls with nested calls to other endpoints 70 | 71 | ## 1.13.1 72 | 73 | Released on 7th May 2020 74 | 75 | Updated to @polkadot/api package v1.13.1 76 | 77 | ## 1.11.1 78 | 79 | Released on 28th April 2020 80 | 81 | Updated to @polkadot/api package v1.11.1 82 | 83 | ## 1.10.1 84 | 85 | Released on 13th April 2020 86 | 87 | Updated to @polkadot/api package v1.10.1 88 | 89 | ### Additions: 90 | * `/api/rpc/rpc/methods` has been added, which returns the `version` and `methods`, a list of RPC methods that are exposed by the node. 91 | * `/api/rpc/system/networkState` has been added, which returns the current state of the network, namely the `peerId`, `listenedAddresses`, `externalAddresses` and `connectedPeers` for the specified node. 92 | * `/api/rpc/system/properties` has been added, which returns the properties defined in the chain spec, namely the `ss58Format`, `tokenDecimals` and `tokenSymbol` for the network of the specified node. 93 | * `/api/query/balances/totalIssuance` has been added, which returns the total amount of units issued in the chain. Value may be in Hex. 94 | * `/api/query/staking/erasTotalStake` has been added, which returns the total amount staked in the specified `era index`, or in the `active` one if it is not specified. Value may be in Hex. 95 | 96 | ## 1.9.1 97 | 98 | Released on 7th April 2020 99 | 100 | Updated to @polkadot/api package v1.9.1 101 | 102 | ### Additions: 103 | * `/api/query/staking/erasRewardPoints` has been added, which returns the `total` and `individual` rewards in the specified `era index`, or in the `active` one if it is not specified. 104 | * `/api/query/staking/erasValidatorReward` has been added, which returns the total validator era payout in the specified `era index`, or in the last finished era (active era - 1) if it is not specified. 105 | 106 | ## 1.8.1 107 | 108 | Released on 25th March 2020 109 | 110 | Updated to @polkadot/api package v1.8.1 111 | 112 | ### Breaking Changes: 113 | * `/api/query/staking/stakers` has been changed to `/api/query/staking/erasStakers`. The `account_address` parameter has been changed to `account_id` and the optional parameter `era_index` has been added. 114 | * `/api/query/staking/currentElected` has been changed to `/api/derive/staking/validators`. The new endpoint now returns both `validators`, the list of validators which are active in the current `session`, as well as `nextElected`, the list of `validators` which will be active in the next `session`. 115 | 116 | ### Additions: 117 | * `/api/query/staking/activeEra` has been added, which returns the `index` and `start` of the `active era` 118 | 119 | ## 1.4.1 120 | 121 | Released on 26th February 2020 122 | 123 | ### Added 124 | 125 | * Updated @polkadot/api package version to use the latest stable version (v1.4.1) 126 | * Updated docs to refer to the latest docker image version 127 | 128 | ## 1.2.1 129 | 130 | Released on 17th February 2020 131 | 132 | ### Added 133 | 134 | * Updated @polkadot/api package version to use the latest stable version (v1.2.1) 135 | * Updated docs to refer to the latest docker image version 136 | 137 | ## 1.1.1 138 | 139 | Released on 6th February 2020 140 | 141 | ### Added 142 | 143 | * Updated @polkadot/api package version to use the latest stable version (v1.1.1) 144 | * Updated docs to refer to the latest docker image version 145 | * Changed license specified in package.json to Apache 2.0 146 | 147 | ### Other 148 | 149 | * Separated the Polkadot API Server from the [PANIC for Polkadot](https://github.com/SimplyVC/panic_polkadot) repo 150 | 151 | ## 1.0.0 152 | 153 | Released on 21st January 2020 154 | 155 | ### Added 156 | 157 | * First version of the Polkadot API Server 158 | -------------------------------------------------------------------------------- /doc/DESIGN_AND_FEATURES.md: -------------------------------------------------------------------------------- 1 | # Design and Features of the Polkadot API Server 2 | 3 | This page will present the inner workings of the API Server as well as the features that one is able to interact with and how. The following points will be presented and discussed: 4 | 5 | - [**Design**](#design) 6 | - [**Complete List of Endpoints**](#complete-list-of-endpoints) 7 | - [**Using the API**](#using-the-api) 8 | 9 | ## Design 10 | 11 | The components involved in the API Server are the following: 12 | - The **Polkadot Nodes** from which the API retrieves information 13 | - This **API Server** which uses the `@polkadot/api` JavaScript package to retrieve data from the Polkadot Nodes 14 | - The **User/Program** which sends `GET Requests` to the `API Server` as per the defined Endpoints, and receives JSON formatted responses from the `API Server` 15 | 16 | The diagram below gives an idea of the various components at play when the API Server is running, and how they interact with each other and the user/program: 17 | 18 | design 19 | 20 | The API Server works as follows: 21 | - The API Server connects to each of these nodes one by one, as specified in the `config/user_config_nodes.ini` file. 22 | - The API connections to each node exist within JS as `promise` objects. 23 | - The API Server opens a port, as specified in the `config/user_config_main.ini` file. 24 | - By communicating through this port, the API Server receives the endpoints specified in the `Complete List of Endpoints` section below, and requests information from the nodes it is connected to accordingly. 25 | - Once the requested information is received from the node, it is formatted as a JSON, and returned. 26 | 27 | ## Complete List of Endpoints 28 | 29 | | API Endpoint | Required Inputs | Optional Inputs | Output | 30 | |---|---|---|---| 31 | | **Miscellaneous** | | | | 32 | | `/api/pingApi` | None | None | `pong` if the API is accessible | 33 | | `/api/pingNode` | `websocket` | None | `pong` if the API could access the Node | 34 | | `/api/getConnectionsList` | None | None | List of `nodes` (websocket_ips) the API is connected to | 35 | | **RPC** | | | | 36 | | `/api/rpc/chain/getBlockHash` | `websocket` | `block_number` | `block hash` of the specified block, or of the latest block if one is not specified | 37 | | `/api/rpc/chain/getFinalizedHead` | `websocket` | None | `block hash` | 38 | | `/api/rpc/chain/getHeader` | `websocket` | `hash` | `header` of the specified block hash, or of the latest block if the hash is not specified | 39 | | `/api/rpc/rpc/methods` | `websocket` | None | `version` and `methods`, a list of RPC methods that are exposed by the node | 40 | | `/api/rpc/system/chain` | `websocket` | None | `system chain` | 41 | | `/api/rpc/system/health` | `websocket` | None | `system health` - `peers`, `isSyncing` | 42 | | `/api/rpc/system/networkState` | `websocket` | None | `peerId`, `listenedAddresses`, `externalAddresses` and `connectedPeers` for the specified node. The current state of the network | 43 | | `/api/rpc/system/properties` | `websocket` | None | `ss58Format`, `tokenDecimals` and `tokenSymbol` for the network of the specified node. Properties defined in the chain spec | 44 | | **Query** | | | | 45 | | `/api/query/balances/totalIssuance` | `websocket` | None | The total amount of units issued in the chain. Value may be in Hex | 46 | | `/api/query/council/members` | `websocket` | None | List of `council members` | 47 | | `/api/query/council/proposalCount` | `websocket` | None | Number of `proposals` | 48 | | `/api/query/council/proposalOf` | `websocket`, `hash` | None | `proposal info` - `end`, `proposalHash`, `treshold`, `delay` | 49 | | `/api/query/council/proposals` | `websocket` | None | List of `proposals` | 50 | | `/api/query/democracy/publicPropCount` | `websocket` | None | Number of `public proposals` | 51 | | `/api/query/democracy/referendumCount` | `websocket` | None | Number of `referendums` | 52 | | `/api/query/democracy/referendumInfoOf` | `websocket`, `referendum_index` | None | `referendum info` - `end`, `proposalHash`, `treshold`, `delay` | 53 | | `/api/query/imOnline/authoredBlocks` | `websocket`, `session_index`, `validator_id` | None | Number of `blocks authored` by the specified `validatorId` in the specified `sessionIndex` | 54 | | `/api/query/imOnline/receivedHeartbeats` | `websocket`, `session_index`, `auth_index` | None | Any data which shows that it is still `online`, despite not having signed any blocks | 55 | | `/api/query/session/currentIndex` | `websocket` | None | `current index` | 56 | | `/api/query/session/disabledValidators` | `websocket` | None | List of `disabled validators` | 57 | | `/api/query/session/validators` | `websocket` | None | List of `validators` | 58 | | `/api/query/staking/activeEra` | `websocket` | None | `index` and `start` of the `active era` | 59 | | `/api/query/staking/bonded` | `websocket`, `account_id` | None | Controller account assigned to the stash account with the specified `account_id`. Will return `null` if the stash doesn't have a controller assigned. | 60 | | `/api/query/staking/erasRewardPoints` | `websocket` | `era_index` | The `total` and `individual` rewards in the specified `era index`, or in the `active` one if it is not specified | 61 | | `/api/query/staking/erasStakers` | `websocket`, `account_id` | `era_index` | `stakers info` - `total balance nominated`, `balance nominated belonging to the owner`, List of `stakers` who have `nominated` and how much they have `nominated` in the specified `era index`, or in the `active` one if it is not specified | 62 | | `/api/query/staking/erasTotalStake` | `websocket` | `era_index` | The total amount staked in the specified `era index`, or in the `active` one if it is not specified. Value may be in Hex | 63 | | `/api/query/staking/erasValidatorReward` | `websocket` | `era_index` | The total validator era payout in the specified `era index`, or in the last finished era (active era - 1) if it is not specified | 64 | | `/api/query/staking/payee` | `websocket`, `account_id` | None | Reward destination address assigned to the stash with the specified `account_id`. Returns json with different keys depending on the type of reward destination: `staked: null` - when reward destination is a stash, `controller: addr` - when reward destination is a controller, and `result: addr` when reward destination is any other address. | 65 | | `/api/query/staking/unappliedSlashes` | `websocket` | `era_index` | List of slashed validators (unapplied) in the specified era, or the current era if an era index is not specified. | 66 | | `/api/query/staking/validators` | `websocket`, `account_id` | Preferences of the validator whose stash is the specified `account_id` (validator commission and blocked status). | 67 | | `/api/query/system/events` | `websocket` | `block_hash` | `events` that happened in the specified `block hash`, or in the latest block if the block hash is not specified | 68 | | **Custom** | | | | 69 | | `/api/custom/getSlashAmount` | `websocket`, `account_address` | `block_hash` | The `balance slashed` (if any) of the specified `account address` in the specified `block hash`, or in the latest block if the block hash is not specified | 70 | | **Derive** | | | | 71 | | `/api/derive/staking/validators` | `websocket` | None | `nextElected` - List of `validators` which will be active in the `next session` and `validators` - List of `validators` which are currently active | 72 | 73 | ## Using the API 74 | For example, the endpoint `/api/rpc/system/health` can be called as follows: `http://localhost:3000/api/rpc/system/health?websocket=ws://1.2.3.4:9944`. 75 | If successful, this will return: 76 | ```json 77 | { 78 | "result": { 79 | "peers": 92, 80 | "isSyncing": false, 81 | "shouldHavePeers": true 82 | } 83 | } 84 | ``` 85 | If an API connection for the node specified in the `websocket` field is not set up, this will return: 86 | ```json 87 | { 88 | "error": "An API for ws://1.2.3.4:9944 needs to be setup before it can be queried" 89 | } 90 | ``` 91 | If an API call without all the required fields is sent, such as `http://localhost:3000/api/query/imOnline/authoredBlocks?websocket=ws://1.2.3.4:9944&session_index=3`, this will return: 92 | ```json 93 | { 94 | "error": "You did not enter the stash account address of the validator that needs to be queried" 95 | } 96 | ``` 97 | 98 | --- 99 | [Back to API front page](../README.md) 100 | -------------------------------------------------------------------------------- /doc/IMG_API_SERVER_DESIGN.drawio: -------------------------------------------------------------------------------- 1 | 7Vnbcto6FP0az7QPyWAbbHgEAmlzaKCH9qR5FLYwOsiWK4tbv74Slq9yuCQmU2bKQyJtXWzttfbSlqWZfX97T0G4+EJciDWj4W41804zDKPTNPg/YdnFFr3RMWOLR5ErbZlhin7BpKO0rpALo0JHRghmKCwaHRIE0GEFG6CUbIrd5gQXnxoCDyqGqQOwan1CLlvE1rZhZ/ZPEHmL5Mm61YlbfJB0liuJFsAlm5zJHGhmnxLC4pK/7UMsvJf45enz7gmPltb9w9foJ/je++fb43838WTDc4akS6AwYPVOLbFcA7yS/poQvOTrZNz6yNnwIfooV892iUspWQUuFNPqmtnbLBCD0xA4onXDWcRtC+Zj2TwnAZOs0LnLeh4GkYC0wcsRo2SZwiJ6pz4WzScuW7pnDSmD2xzo0g33kPiQ0R3vIls7EtCE0k1Z32T80E1pW+S4YUkbkJT00pkzt/OC9PwZKDQVFJ55yLzs9kbRxf9DxnbSx2DFCDcRyhbEIwHAI0LCCihEHQZuV0QYr88wcZaxaYjEy++fwWuyf/s4WhjMIO4BZ+ntX7RPMKG8KSABFFO5PCrlWrKXG2TWM9EW8x3EWoKbBC+FGDC0LopCFZBypglBAct1IfN5BFkxwJI+iVgaRV6lPEvmYIB6kMlRJcqkb/56FrUUFj0ShUTHcbhqml2GRPo7ksgukUgvkSgiK+rAOki0NKPmqD1+/vQ8ApsHE0Sr8biCRN8jSDVB7SH/O6HEo8BXaMUhCkVx5eOuwwQiPaHHiO/DIwHYhESIIRIIDhDGiP8ykpphzve/3BxdjDwxlgmS9ciKYRTAfpoxlIg659TKzTawGg27e8H9xLJKG0pD3VDsiv0k3XjesqFUoqgbCoyqEmRh6Yg9GTlFL8ItYj9y5Wfh6FujJat32yR6RWWXhjJ//R/5Sn6YqGfj9rVk4HnYxEFwPK+JFfeAoxK/HFWCHJStCihbb1QIyaRmkUjNxmnBr8xTynDM1sV2omr6tRX6/QujkAQRVGjIY4wViQdksDucCpBWqICPXDfeqmCEfoHZfipBolAsaL/EVk9r3Z2mMSrzDodUWRbSo4t8ES1/OqiSixseDh3bKsp8LfS56RRHqPtNbQirB4dXC0yjIDD2iQKjFwXGvpzAHBSOvMIcUqK/AlMr/ewKgfm5gpF4RbIWCYuFhazMRMkTpSc4m/LkU4bDVSpQEnO1KJDZ1gsQ3tQjQbpxa9qd/O/dJEk9RZ8vSe+jLteWligJbrt1nbJhKRTpTj5zwyBww/ihZdn4cD/4xjtIdVG/hV2NeCTR8Wbx4NphWM0LZC+2cdu23k0t1IPuqxOYs05IF8pD/hClqCu/UBSnVVKcSytFR6HHw3T8yC1DQn3AGHRVrbj+M04SFHVkGE3LMIsZhl4LxUpEuJxEJKFVkIgYcxetE9Cz+5K4iT8q11oxYL/jnNh3CmkumS13f+EbXEiJA8Udy7GrmVnKmXH8RU2r/H423P8u+P3MbB+/kGlXaNYr7mN4Nbuwi4mS3Xuag98= -------------------------------------------------------------------------------- /doc/IMG_API_SERVER_DESIGN_5x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyStaking/polkadot_api_server/8d4014f4cdd0a0dd31c733ae1647b66f9cb269d6/doc/IMG_API_SERVER_DESIGN_5x.png -------------------------------------------------------------------------------- /doc/IMG_POLKADOT_API_SERVER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyStaking/polkadot_api_server/8d4014f4cdd0a0dd31c733ae1647b66f9cb269d6/doc/IMG_POLKADOT_API_SERVER.png -------------------------------------------------------------------------------- /doc/INSTALL_AND_RUN.md: -------------------------------------------------------------------------------- 1 | # Install and Run the Polkadot API Server 2 | 3 | Please Note: For this Polkadot API Server to run correctly, the version of the `@polkadot/api` JavaScript package, defined in `package.json`, must match the API version being run by the nodes.\ 4 | You can check the latest version of the `@polkadot/api` JavaScript package from [here](https://www.npmjs.com/package/@polkadot/api). 5 | 6 | ## Configuring the Nodes 7 | In order for the API to be able to retrieve data from the Substrate/Polkadot Nodes, the nodes must be run with the following flags (in addition to any other flags you would like to use): 8 | ```bash 9 | --pruning=archive --ws-port=9944 --ws-external --rpc-cors=all 10 | ``` 11 | As of v0.7.10, the Validator must be run with these flags instead: 12 | ```bash 13 | --pruning=archive --ws-port=9944 --unsafe-ws-external --rpc-cors=all 14 | ``` 15 | 16 | ## Configuring the API 17 | Configuring the API involves setting up two config files. This is a strict requirement for the API to be able to run. 18 | 19 | Start off by cloning this repository and navigating into it: 20 | ```bash 21 | git clone https://github.com/SimplyVC/polkadot_api_server 22 | cd polkadot_api_server 23 | ``` 24 | 25 | The API can be configured in either of two ways, by [using the setup script](#using-the-setup-script), or [manually](#manually). 26 | 27 | ### Using the Setup Script 28 | Please note that you will need to have installed Python before using this script. 29 | To install Python, pip and pipenv follow [this guide](INSTALL_PYTHON.md). 30 | 31 | You can run the Python script `run_setup.py` which will guide you through the configuration process, and ask you to enter the values one by one. This can be done by running the following, starting from the root project directory, `polkadot_api_server`: 32 | ```bash 33 | pipenv sync 34 | pipenv run python run_setup.py 35 | ``` 36 | 37 | ### Manually 38 | Alternatively, for advanced users, you can make a copy of the `example_*.ini` files inside the `config` folder, without the `example_` prefix, and manually change the values as required. 39 | 40 | ## Installing the API and Dependencies 41 | This section will guide you through the installation of the API and any of its dependencies. 42 | 43 | We recommend that the API is installed on a Linux system, given the simpler installation and running process. 44 | Nevertheless, instructions on how to install the API on a Windows system are also provided. 45 | 46 | You can either [run the API from source](#running-from-source) or [run the API using Docker](#run-using-docker). 47 | This affects what dependencies you will need to install. 48 | 49 | ### Running from Source 50 | Running the API from source only requires you to install Node.js. 51 | 52 | #### Install Node.js 53 | ##### On Ubuntu 54 | ```bash 55 | curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - 56 | sudo apt-get install -y nodejs 57 | ``` 58 | [Source](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions-enterprise-linux-fedora-and-snap-packages) 59 | 60 | ###### On Windows 61 | Download the latest LTS Release of Node.js from [here](https://nodejs.org/en/download/) - Choose the `Windows Installer (.msi)` for your Operating System type (32-bit vs 64-bit). 62 | Once the file is downloaded, double click on it to start the installation. The process is very simple, you can leave all the default options and simply press Next throughout the process. 63 | 64 | #### Install Packages needed by the API 65 | Install the packages defined in `package.json` by running: 66 | ```bash 67 | npm install 68 | ``` 69 | 70 | #### Running the API 71 | After having installed Node.js and the packages required by the API, you can now run the API as follows from the project directory: 72 | ```bash 73 | bash run_api.sh 74 | ``` 75 | 76 | #### Running the API as a Linux Service 77 | Running the API as a service means that it starts up automatically on boot and restarts automatically if it runs into some issue and stops running. To do so, we recommend the following steps: 78 | ```bash 79 | # Add a new user to run the API 80 | sudo adduser 81 | 82 | # Grant permissions 83 | sudo chown -R : / # ownership of api 84 | sudo chmod +x /run_api.sh # execute permission for runner 85 | ``` 86 | 87 | The service file will now be created: 88 | 89 | ```bash 90 | # Create the service file 91 | sudo nano /etc/systemd/system/polkadot_api_server.service 92 | ``` 93 | 94 | It should contain the following, replacing `` with the created user's name and the two `` with the API Server's installation directory: 95 | 96 | ```bash 97 | [Unit] 98 | Description=Polkadot API Server 99 | After=network.target 100 | StartLimitIntervalSec=0 101 | 102 | [Service] 103 | Type=simple 104 | Restart=always 105 | User= 106 | TimeoutStopSec=90s 107 | WorkingDirectory=/ 108 | ExecStart=/bin/bash /run_api.sh 109 | 110 | [Install] 111 | WantedBy=multi-user.target 112 | ``` 113 | 114 | Lastly, we will *enable* and *start* the alerter service: 115 | 116 | ```bash 117 | sudo systemctl enable polkadot_api_server.service 118 | sudo systemctl start polkadot_api_server.service 119 | ``` 120 | 121 | Check out `systemctl status polkadot_api_server` to confirm that the API is running. 122 | 123 | ### Run using Docker 124 | To run the API using Docker, you shouldn't be surprised to find out that you need to install Docker. 125 | 126 | You will then obtain the Docker image, make sure that the config files are where they should be, and run everything. 127 | 128 | #### Installing Docker on your Machine 129 | To install Docker on your machine, follow [this guide](INSTALL_DOCKER.md) 130 | 131 | #### Obtaining the Docker Image 132 | This part can be done in either of two ways, either by building the Docker image yourself, or by downloading it from Docker Hub. 133 | 134 | ##### Building the Docker Image 135 | First start off by cloning this repository: 136 | ```bash 137 | git clone https://github.com/SimplyVC/polkadot_api_server 138 | ``` 139 | 140 | Then run the following commands to build the image: 141 | ```bash 142 | cd polkadot_api_server 143 | docker build -t simplyvc/polkadot_api_server:1.29.1 . 144 | ``` 145 | 146 | ##### Downloading the Pre-Built Docker Image from DockerHub 147 | The pre-built Docker image can simply be downloaded by running the following command: 148 | ```bash 149 | docker pull simplyvc/polkadot_api_server:1.29.1 150 | ``` 151 | 152 | #### Config Files Directory and Permissions 153 | The config files needed by the Docker image are the same as those generated in the `Configuring the API` section above.\ 154 | These config files can be moved to any directory of your choosing ``. 155 | 156 | ##### On Ubuntu 157 | If you created a new user `` earlier on, set the permissions as follows: 158 | ```bash 159 | sudo chown -R : 160 | ``` 161 | 162 | ##### On Windows 163 | No further steps are required. 164 | 165 | #### Running the Docker Image 166 | Now that the Docker image is on your machine, and you have written configurations for it, you can run it as follows, where `` is the **full path** to the folder containing the previously created config files: 167 | ```bash 168 | docker run -p 3000:3000 \ 169 | -v :/opt/polkadot_api_server/config:ro \ 170 | -d simplyvc/polkadot_api_server:1.29.1 171 | ``` 172 | 173 | Note: The port after `-p` and before the `:` can be used to route a port from the machine to the internal port of the Docker. If changing this, any program which refers to the API Docker container must refer to this port.\ 174 | Example: with `5678`:3000, the the API URL must look like `http://1.2.3.4:5678`, i.e. the port must match `5678`, and not 3000. 175 | 176 | ## Confirming the API Works 177 | If you wish to make sure that the API is running, the following should return `{"result":"pong"}`: 178 | ```bash 179 | curl -X GET http://localhost:3000/api/pingApi 180 | ``` 181 | 182 | If you wish to check the API's connection to a node, you can run the following for some node ``: 183 | ```bash 184 | curl -X GET http://localhost:3000/api/pingNode?websocket=ws:// 185 | ``` 186 | 187 | --- 188 | [Back to API front page](../README.md) 189 | -------------------------------------------------------------------------------- /doc/INSTALL_DOCKER.md: -------------------------------------------------------------------------------- 1 | # Installing Docker 2 | 3 | ## On Ubuntu 4 | First, install Docker on your machine: 5 | ```bash 6 | sudo apt update 7 | sudo apt install apt-transport-https ca-certificates curl software-properties-common 8 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 9 | sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" 10 | sudo apt update 11 | apt-cache policy docker-ce 12 | sudo apt install docker-ce 13 | ``` 14 | [Source](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04#step-1-%E2%80%94-installing-docker) 15 | 16 | In order to confirm that Docker has been installed correctly, run: 17 | ```bash 18 | docker --version 19 | ``` 20 | 21 | ### Creating a new user 22 | You should create a new user from which you will run the Docker images. This is not required, but suggested for security reasons. 23 | ```bash 24 | sudo adduser polkadot_api_server 25 | ``` 26 | 27 | You must then grant this user access to use Docker. 28 | ```bash 29 | sudo usermod -aG docker polkadot_api_server 30 | ``` 31 | [Source](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04#step-2-%E2%80%94-executing-the-docker-command-without-sudo-\(optional\)) 32 | 33 | In order to log in to this user, you can simply run: 34 | ```bash 35 | su polkadot_api_server 36 | ``` 37 | 38 | ## On Windows 39 | Download and Install the `Stable` version of `Docker Desktop for Windows` from [here](https://hub.docker.com/editions/community/docker-ce-desktop-windows).\ 40 | Once it has installed, you must Log out and back in in order for the installation to complete 41 | 42 | In order to confirm that Docker has been installed correctly, inside `Command Prompt` or `Powershell` run: 43 | ```bash 44 | docker --version 45 | ``` 46 | [Source](https://docs.docker.com/docker-for-windows/) 47 | 48 | --- 49 | [Back to API installation page](INSTALL_AND_RUN.md) -------------------------------------------------------------------------------- /doc/INSTALL_PYTHON.md: -------------------------------------------------------------------------------- 1 | # Python (with pip and pipenv) 2 | 3 | 1. To install **Python v3.7.4 or greater**, you can [follow this guide](https://realpython.com/installing-python/). 4 | 2. To install **pip** package manager: 5 | - On Linux, run: `apt-get install python3-pip`. 6 | - On Windows, it should come included in the installation. 7 | 3. To install **pipenv** packaging tool, run `pip install pipenv`. 8 | (If 'pip' is not found, try using 'pip3' instead.) 9 | 10 | **At the end, you should be able to:** 11 | 1. Get the Python version by running `python --version`. 12 | (You may have to replace 'python' with 'python3.6', 'python3.7', etc.) 13 | 2. Get the pip version by running `pip --version`. 14 | 3. Get the pipenv version by running `pipenv --version`. 15 | 16 | --- 17 | [Back to API installation page](INSTALL_AND_RUN.md) -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polkadot-api-server", 3 | "version": "1.29.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/runtime": { 8 | "version": "7.14.8", 9 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.8.tgz", 10 | "integrity": "sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==", 11 | "requires": { 12 | "regenerator-runtime": "^0.13.4" 13 | } 14 | }, 15 | "@polkadot/api": { 16 | "version": "5.1.1", 17 | "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-5.1.1.tgz", 18 | "integrity": "sha512-dBumQsSiHpyQ2kawpE2KbAROJxA2L06ISJ7G2ZGiOwQbFwGQWqN6i2inCKNY7smBCJrQAtCfYQBZWOSdn0avRw==", 19 | "requires": { 20 | "@babel/runtime": "^7.14.6", 21 | "@polkadot/api-derive": "5.1.1", 22 | "@polkadot/keyring": "^7.0.2", 23 | "@polkadot/rpc-core": "5.1.1", 24 | "@polkadot/rpc-provider": "5.1.1", 25 | "@polkadot/types": "5.1.1", 26 | "@polkadot/types-known": "5.1.1", 27 | "@polkadot/util": "^7.0.2", 28 | "@polkadot/util-crypto": "^7.0.2", 29 | "eventemitter3": "^4.0.7", 30 | "rxjs": "^7.2.0" 31 | } 32 | }, 33 | "@polkadot/api-derive": { 34 | "version": "5.1.1", 35 | "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-5.1.1.tgz", 36 | "integrity": "sha512-3Y0T5HwdqUXOra7CwgFEqBzZ7eNYgeBM8WsA0bEz+WuaCkldTrnFFMpjUhLwpf/CHTdEbn0dPoqwh6Q15dzJVA==", 37 | "requires": { 38 | "@babel/runtime": "^7.14.6", 39 | "@polkadot/api": "5.1.1", 40 | "@polkadot/rpc-core": "5.1.1", 41 | "@polkadot/types": "5.1.1", 42 | "@polkadot/util": "^7.0.2", 43 | "@polkadot/util-crypto": "^7.0.2", 44 | "rxjs": "^7.2.0" 45 | } 46 | }, 47 | "@polkadot/keyring": { 48 | "version": "7.0.2", 49 | "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-7.0.2.tgz", 50 | "integrity": "sha512-EV2lR4wV4uX4UHpBjcZgqKKOFvGR2fN2SvUXP/VLE+u2lK7wjNlx72MI9IbhlqJEg3j7SPqXH7BEw60w690vDQ==", 51 | "requires": { 52 | "@babel/runtime": "^7.14.6", 53 | "@polkadot/util": "7.0.2", 54 | "@polkadot/util-crypto": "7.0.2" 55 | } 56 | }, 57 | "@polkadot/networks": { 58 | "version": "7.0.2", 59 | "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-7.0.2.tgz", 60 | "integrity": "sha512-KP8T1uaUkwzmsOZPnpJ+EZWOTR26v+BxxeV9kc7NoD3UrSVHcHI7uz1mKfhmTn9gSDf04DqQ1zmFr6PIMlhncw==", 61 | "requires": { 62 | "@babel/runtime": "^7.14.6" 63 | } 64 | }, 65 | "@polkadot/rpc-core": { 66 | "version": "5.1.1", 67 | "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-5.1.1.tgz", 68 | "integrity": "sha512-qzx1D7SpPk6lRMLHJZDKUFFXLRir2JV+r7SofmBJwpJxzBMAdRfEoD1qZI8H0Vz3BB5hz7vGFEdyNlYUOhHWaQ==", 69 | "requires": { 70 | "@babel/runtime": "^7.14.6", 71 | "@polkadot/rpc-provider": "5.1.1", 72 | "@polkadot/types": "5.1.1", 73 | "@polkadot/util": "^7.0.2", 74 | "rxjs": "^7.2.0" 75 | } 76 | }, 77 | "@polkadot/rpc-provider": { 78 | "version": "5.1.1", 79 | "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-5.1.1.tgz", 80 | "integrity": "sha512-5/KeJxQcOfae5GT1CnuArHJREl+eh6wfIUjJaeO48x4caIbCgm4jnubjrSwhONLzSLDObsoomqJwA4zUh6OLQw==", 81 | "requires": { 82 | "@babel/runtime": "^7.14.6", 83 | "@polkadot/types": "5.1.1", 84 | "@polkadot/util": "^7.0.2", 85 | "@polkadot/util-crypto": "^7.0.2", 86 | "@polkadot/x-fetch": "^7.0.2", 87 | "@polkadot/x-global": "^7.0.2", 88 | "@polkadot/x-ws": "^7.0.2", 89 | "eventemitter3": "^4.0.7" 90 | } 91 | }, 92 | "@polkadot/types": { 93 | "version": "5.1.1", 94 | "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-5.1.1.tgz", 95 | "integrity": "sha512-9NOLghfAV+P3wZys3DHL350TMY+UL/lRB4ZOAwRFBfye2FYVklNqzEkudKPDSEaKqRpx7KRaETpC/txWQLAwfw==", 96 | "requires": { 97 | "@babel/runtime": "^7.14.6", 98 | "@polkadot/util": "^7.0.2", 99 | "@polkadot/util-crypto": "^7.0.2", 100 | "rxjs": "^7.2.0" 101 | } 102 | }, 103 | "@polkadot/types-known": { 104 | "version": "5.1.1", 105 | "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-5.1.1.tgz", 106 | "integrity": "sha512-XlTAB2T5lxdTXVz5Yscng+EjV3V/wUapoEm+Ac+AjEeqXz6dhYTaiap6IQGtxLWmrMmhaeH5xIAEHVkWwEYcNA==", 107 | "requires": { 108 | "@babel/runtime": "^7.14.6", 109 | "@polkadot/networks": "^7.0.2", 110 | "@polkadot/types": "5.1.1", 111 | "@polkadot/util": "^7.0.2" 112 | } 113 | }, 114 | "@polkadot/util": { 115 | "version": "7.0.2", 116 | "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-7.0.2.tgz", 117 | "integrity": "sha512-NJHRprsEZ61p9Ch0CSch0O8lHFC9rzYinOSPYmbsur7upZJKNzeqnQ7GxiMI/4u9lznfAmD/TW6BTwkWHenncg==", 118 | "requires": { 119 | "@babel/runtime": "^7.14.6", 120 | "@polkadot/x-textdecoder": "7.0.2", 121 | "@polkadot/x-textencoder": "7.0.2", 122 | "@types/bn.js": "^4.11.6", 123 | "bn.js": "^4.11.9", 124 | "camelcase": "^5.3.1", 125 | "ip-regex": "^4.3.0" 126 | } 127 | }, 128 | "@polkadot/util-crypto": { 129 | "version": "7.0.2", 130 | "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-7.0.2.tgz", 131 | "integrity": "sha512-d4X+4YQOiVqIV82fU1DaOWMRKawpDVHZBsCMkE6twYadpc/BlNfrksiUhmTUklZPPMF6jIfLPjz8j7fH7GZeCQ==", 132 | "requires": { 133 | "@babel/runtime": "^7.14.6", 134 | "@polkadot/networks": "7.0.2", 135 | "@polkadot/util": "7.0.2", 136 | "@polkadot/wasm-crypto": "^4.1.2", 137 | "@polkadot/x-randomvalues": "7.0.2", 138 | "base-x": "^3.0.8", 139 | "base64-js": "^1.5.1", 140 | "blakejs": "^1.1.1", 141 | "bn.js": "^4.11.9", 142 | "create-hash": "^1.2.0", 143 | "elliptic": "^6.5.4", 144 | "hash.js": "^1.1.7", 145 | "js-sha3": "^0.8.0", 146 | "scryptsy": "^2.1.0", 147 | "tweetnacl": "^1.0.3", 148 | "xxhashjs": "^0.2.2" 149 | } 150 | }, 151 | "@polkadot/wasm-crypto": { 152 | "version": "4.1.2", 153 | "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-4.1.2.tgz", 154 | "integrity": "sha512-2EKdOjIrD2xHP2rC+0G/3Qo6926nL/18vCFkd34lBd9zP9YNF2GDEtDY+zAeDIRFKe1sQHTpsKgNdYSWoV2eBg==", 155 | "requires": { 156 | "@babel/runtime": "^7.14.6", 157 | "@polkadot/wasm-crypto-asmjs": "^4.1.2", 158 | "@polkadot/wasm-crypto-wasm": "^4.1.2" 159 | } 160 | }, 161 | "@polkadot/wasm-crypto-asmjs": { 162 | "version": "4.1.2", 163 | "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-4.1.2.tgz", 164 | "integrity": "sha512-3Q+vVUxDAC2tXgKMM3lKzx2JW+tarDpTjkvdxIKATyi8Ek69KkUqvMyJD0VL/iFZOFZED0YDX9UU4XOJ/astlg==", 165 | "requires": { 166 | "@babel/runtime": "^7.14.6" 167 | } 168 | }, 169 | "@polkadot/wasm-crypto-wasm": { 170 | "version": "4.1.2", 171 | "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-4.1.2.tgz", 172 | "integrity": "sha512-/l4IBEdQ41szHdHkuF//z1qr+XmWuLHlpBA7s9Eb221m1Fir6AKoCHoh1hp1r3v0ecZYLKvak1B225w6JAU3Fg==", 173 | "requires": { 174 | "@babel/runtime": "^7.14.6" 175 | } 176 | }, 177 | "@polkadot/x-fetch": { 178 | "version": "7.0.2", 179 | "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-7.0.2.tgz", 180 | "integrity": "sha512-S2akk8RfPXcO51mnmOFzTe3aLL26eCVMlq40CTvGQfv/BWUAxQTJYhOfkVb+DpoTT+SVaIRW8I6tuyX4FQH3Bg==", 181 | "requires": { 182 | "@babel/runtime": "^7.14.6", 183 | "@polkadot/x-global": "7.0.2", 184 | "@types/node-fetch": "^2.5.11", 185 | "node-fetch": "^2.6.1" 186 | } 187 | }, 188 | "@polkadot/x-global": { 189 | "version": "7.0.2", 190 | "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-7.0.2.tgz", 191 | "integrity": "sha512-2M751KKNX5Rne8cO/Xx014eI8uq7SCr97Zl+bXaeZqChjgy0AeNd8/E4g2zcvjXXU6EiB+p51oCzEv3j5psq3w==", 192 | "requires": { 193 | "@babel/runtime": "^7.14.6" 194 | } 195 | }, 196 | "@polkadot/x-randomvalues": { 197 | "version": "7.0.2", 198 | "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-7.0.2.tgz", 199 | "integrity": "sha512-KmfEL75dU2jvLEJBLScoNXnC489jW0LvFPeFXD4uoLNRaaLesJ+wWcbGN+Ek1lHJDnGCNfk0AdHfJCWNyxE96A==", 200 | "requires": { 201 | "@babel/runtime": "^7.14.6", 202 | "@polkadot/x-global": "7.0.2" 203 | } 204 | }, 205 | "@polkadot/x-textdecoder": { 206 | "version": "7.0.2", 207 | "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-7.0.2.tgz", 208 | "integrity": "sha512-eFJFdAS6K9nt/9i+jnehSKPHrvkFwkS5WdtqkNetT530dpD/BWg7wRTZApJQhN7Q8A6zSGvZnee1EJt4aNK0Bw==", 209 | "requires": { 210 | "@babel/runtime": "^7.14.6", 211 | "@polkadot/x-global": "7.0.2" 212 | } 213 | }, 214 | "@polkadot/x-textencoder": { 215 | "version": "7.0.2", 216 | "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-7.0.2.tgz", 217 | "integrity": "sha512-6l4f6VlOMXz83R1QanA21EHPbpfn1odKiV9FFa9dY9bIuyMUwtDkKW+XsQqcvWJCsa26OSwGwDHQ9ru6rV+6CQ==", 218 | "requires": { 219 | "@babel/runtime": "^7.14.6", 220 | "@polkadot/x-global": "7.0.2" 221 | } 222 | }, 223 | "@polkadot/x-ws": { 224 | "version": "7.0.2", 225 | "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-7.0.2.tgz", 226 | "integrity": "sha512-UfzOBQFBcBcbWDMLSUvb0uh112JbDgXhzTGC9yXABwD8/WZTbi7E7TpcXDpu8xVaGfE4A3aYO9KVxIi+PrCfrw==", 227 | "requires": { 228 | "@babel/runtime": "^7.14.6", 229 | "@polkadot/x-global": "7.0.2", 230 | "@types/websocket": "^1.0.3", 231 | "websocket": "^1.0.34" 232 | } 233 | }, 234 | "@types/bn.js": { 235 | "version": "4.11.6", 236 | "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", 237 | "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", 238 | "requires": { 239 | "@types/node": "*" 240 | } 241 | }, 242 | "@types/node": { 243 | "version": "16.4.1", 244 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.1.tgz", 245 | "integrity": "sha512-UW7cbLqf/Wu5XH2RKKY1cHwUNLicIDRLMraYKz+HHAerJ0ZffUEk+fMnd8qU2JaS6cAy0r8tsaf7yqHASf/Y0Q==" 246 | }, 247 | "@types/node-fetch": { 248 | "version": "2.5.12", 249 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", 250 | "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", 251 | "requires": { 252 | "@types/node": "*", 253 | "form-data": "^3.0.0" 254 | } 255 | }, 256 | "@types/websocket": { 257 | "version": "1.0.3", 258 | "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.3.tgz", 259 | "integrity": "sha512-ZdoTSwmDsKR7l1I8fpfQtmTI/hUwlOvE3q0iyJsp4tXU0MkdrYowimDzwxjhQvxU4qjhHLd3a6ig0OXRbLgIdw==", 260 | "requires": { 261 | "@types/node": "*" 262 | } 263 | }, 264 | "accepts": { 265 | "version": "1.3.7", 266 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 267 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 268 | "requires": { 269 | "mime-types": "~2.1.24", 270 | "negotiator": "0.6.2" 271 | } 272 | }, 273 | "array-flatten": { 274 | "version": "1.1.1", 275 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 276 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 277 | }, 278 | "asynckit": { 279 | "version": "0.4.0", 280 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 281 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 282 | }, 283 | "await-timeout": { 284 | "version": "1.1.1", 285 | "resolved": "https://registry.npmjs.org/await-timeout/-/await-timeout-1.1.1.tgz", 286 | "integrity": "sha512-gsDXAS6XVc4Jt+7S92MPX6Noq69bdeXUPEaXd8dk3+yVr629LTDLxNt4j1ycBbrU+AStK2PhKIyNIM+xzWMVOQ==" 287 | }, 288 | "base-x": { 289 | "version": "3.0.8", 290 | "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", 291 | "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", 292 | "requires": { 293 | "safe-buffer": "^5.0.1" 294 | } 295 | }, 296 | "base64-js": { 297 | "version": "1.5.1", 298 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 299 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 300 | }, 301 | "blakejs": { 302 | "version": "1.1.1", 303 | "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.1.tgz", 304 | "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==" 305 | }, 306 | "bn.js": { 307 | "version": "4.12.0", 308 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", 309 | "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" 310 | }, 311 | "body-parser": { 312 | "version": "1.19.0", 313 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 314 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 315 | "requires": { 316 | "bytes": "3.1.0", 317 | "content-type": "~1.0.4", 318 | "debug": "2.6.9", 319 | "depd": "~1.1.2", 320 | "http-errors": "1.7.2", 321 | "iconv-lite": "0.4.24", 322 | "on-finished": "~2.3.0", 323 | "qs": "6.7.0", 324 | "raw-body": "2.4.0", 325 | "type-is": "~1.6.17" 326 | }, 327 | "dependencies": { 328 | "debug": { 329 | "version": "2.6.9", 330 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 331 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 332 | "requires": { 333 | "ms": "2.0.0" 334 | } 335 | }, 336 | "ms": { 337 | "version": "2.0.0", 338 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 339 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 340 | } 341 | } 342 | }, 343 | "brorand": { 344 | "version": "1.1.0", 345 | "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", 346 | "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" 347 | }, 348 | "bufferutil": { 349 | "version": "4.0.3", 350 | "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", 351 | "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", 352 | "requires": { 353 | "node-gyp-build": "^4.2.0" 354 | } 355 | }, 356 | "bytes": { 357 | "version": "3.1.0", 358 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 359 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 360 | }, 361 | "camelcase": { 362 | "version": "5.3.1", 363 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 364 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" 365 | }, 366 | "cipher-base": { 367 | "version": "1.0.4", 368 | "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", 369 | "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", 370 | "requires": { 371 | "inherits": "^2.0.1", 372 | "safe-buffer": "^5.0.1" 373 | } 374 | }, 375 | "combined-stream": { 376 | "version": "1.0.8", 377 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 378 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 379 | "requires": { 380 | "delayed-stream": "~1.0.0" 381 | } 382 | }, 383 | "configparser": { 384 | "version": "0.3.6", 385 | "resolved": "https://registry.npmjs.org/configparser/-/configparser-0.3.6.tgz", 386 | "integrity": "sha512-qCYjKDEK69qRtm3n+RYM55b8XiH2uKfnI6qOKg+ZLrKSG5vY59RBcZhLxxVd18+B5C5Zc7Jy8KpFsB5YOAD+hw==", 387 | "requires": { 388 | "mkdirp": "^0.5.1" 389 | } 390 | }, 391 | "content-disposition": { 392 | "version": "0.5.3", 393 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 394 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 395 | "requires": { 396 | "safe-buffer": "5.1.2" 397 | } 398 | }, 399 | "content-type": { 400 | "version": "1.0.4", 401 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 402 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 403 | }, 404 | "cookie": { 405 | "version": "0.4.0", 406 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 407 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 408 | }, 409 | "cookie-signature": { 410 | "version": "1.0.6", 411 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 412 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 413 | }, 414 | "create-hash": { 415 | "version": "1.2.0", 416 | "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", 417 | "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", 418 | "requires": { 419 | "cipher-base": "^1.0.1", 420 | "inherits": "^2.0.1", 421 | "md5.js": "^1.3.4", 422 | "ripemd160": "^2.0.1", 423 | "sha.js": "^2.4.0" 424 | } 425 | }, 426 | "cuint": { 427 | "version": "0.2.2", 428 | "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", 429 | "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=" 430 | }, 431 | "d": { 432 | "version": "1.0.1", 433 | "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", 434 | "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", 435 | "requires": { 436 | "es5-ext": "^0.10.50", 437 | "type": "^1.0.1" 438 | } 439 | }, 440 | "debug": { 441 | "version": "2.6.9", 442 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 443 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 444 | "requires": { 445 | "ms": "2.0.0" 446 | } 447 | }, 448 | "delayed-stream": { 449 | "version": "1.0.0", 450 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 451 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 452 | }, 453 | "depd": { 454 | "version": "1.1.2", 455 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 456 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 457 | }, 458 | "destroy": { 459 | "version": "1.0.4", 460 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 461 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 462 | }, 463 | "ee-first": { 464 | "version": "1.1.1", 465 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 466 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 467 | }, 468 | "elliptic": { 469 | "version": "6.5.4", 470 | "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", 471 | "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", 472 | "requires": { 473 | "bn.js": "^4.11.9", 474 | "brorand": "^1.1.0", 475 | "hash.js": "^1.0.0", 476 | "hmac-drbg": "^1.0.1", 477 | "inherits": "^2.0.4", 478 | "minimalistic-assert": "^1.0.1", 479 | "minimalistic-crypto-utils": "^1.0.1" 480 | } 481 | }, 482 | "encodeurl": { 483 | "version": "1.0.2", 484 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 485 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 486 | }, 487 | "es5-ext": { 488 | "version": "0.10.53", 489 | "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", 490 | "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", 491 | "requires": { 492 | "es6-iterator": "~2.0.3", 493 | "es6-symbol": "~3.1.3", 494 | "next-tick": "~1.0.0" 495 | } 496 | }, 497 | "es6-iterator": { 498 | "version": "2.0.3", 499 | "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", 500 | "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", 501 | "requires": { 502 | "d": "1", 503 | "es5-ext": "^0.10.35", 504 | "es6-symbol": "^3.1.1" 505 | } 506 | }, 507 | "es6-symbol": { 508 | "version": "3.1.3", 509 | "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", 510 | "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", 511 | "requires": { 512 | "d": "^1.0.1", 513 | "ext": "^1.1.2" 514 | } 515 | }, 516 | "escape-html": { 517 | "version": "1.0.3", 518 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 519 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 520 | }, 521 | "etag": { 522 | "version": "1.8.1", 523 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 524 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 525 | }, 526 | "eventemitter3": { 527 | "version": "4.0.7", 528 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", 529 | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" 530 | }, 531 | "express": { 532 | "version": "4.17.1", 533 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 534 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 535 | "requires": { 536 | "accepts": "~1.3.7", 537 | "array-flatten": "1.1.1", 538 | "body-parser": "1.19.0", 539 | "content-disposition": "0.5.3", 540 | "content-type": "~1.0.4", 541 | "cookie": "0.4.0", 542 | "cookie-signature": "1.0.6", 543 | "debug": "2.6.9", 544 | "depd": "~1.1.2", 545 | "encodeurl": "~1.0.2", 546 | "escape-html": "~1.0.3", 547 | "etag": "~1.8.1", 548 | "finalhandler": "~1.1.2", 549 | "fresh": "0.5.2", 550 | "merge-descriptors": "1.0.1", 551 | "methods": "~1.1.2", 552 | "on-finished": "~2.3.0", 553 | "parseurl": "~1.3.3", 554 | "path-to-regexp": "0.1.7", 555 | "proxy-addr": "~2.0.5", 556 | "qs": "6.7.0", 557 | "range-parser": "~1.2.1", 558 | "safe-buffer": "5.1.2", 559 | "send": "0.17.1", 560 | "serve-static": "1.14.1", 561 | "setprototypeof": "1.1.1", 562 | "statuses": "~1.5.0", 563 | "type-is": "~1.6.18", 564 | "utils-merge": "1.0.1", 565 | "vary": "~1.1.2" 566 | }, 567 | "dependencies": { 568 | "debug": { 569 | "version": "2.6.9", 570 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 571 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 572 | "requires": { 573 | "ms": "2.0.0" 574 | } 575 | }, 576 | "ms": { 577 | "version": "2.0.0", 578 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 579 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 580 | } 581 | } 582 | }, 583 | "ext": { 584 | "version": "1.4.0", 585 | "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", 586 | "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", 587 | "requires": { 588 | "type": "^2.0.0" 589 | }, 590 | "dependencies": { 591 | "type": { 592 | "version": "2.5.0", 593 | "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", 594 | "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==" 595 | } 596 | } 597 | }, 598 | "finalhandler": { 599 | "version": "1.1.2", 600 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 601 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 602 | "requires": { 603 | "debug": "2.6.9", 604 | "encodeurl": "~1.0.2", 605 | "escape-html": "~1.0.3", 606 | "on-finished": "~2.3.0", 607 | "parseurl": "~1.3.3", 608 | "statuses": "~1.5.0", 609 | "unpipe": "~1.0.0" 610 | }, 611 | "dependencies": { 612 | "debug": { 613 | "version": "2.6.9", 614 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 615 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 616 | "requires": { 617 | "ms": "2.0.0" 618 | } 619 | }, 620 | "ms": { 621 | "version": "2.0.0", 622 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 623 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 624 | } 625 | } 626 | }, 627 | "form-data": { 628 | "version": "3.0.1", 629 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", 630 | "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", 631 | "requires": { 632 | "asynckit": "^0.4.0", 633 | "combined-stream": "^1.0.8", 634 | "mime-types": "^2.1.12" 635 | } 636 | }, 637 | "forwarded": { 638 | "version": "0.1.2", 639 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 640 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 641 | }, 642 | "fresh": { 643 | "version": "0.5.2", 644 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 645 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 646 | }, 647 | "hash-base": { 648 | "version": "3.1.0", 649 | "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", 650 | "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", 651 | "requires": { 652 | "inherits": "^2.0.4", 653 | "readable-stream": "^3.6.0", 654 | "safe-buffer": "^5.2.0" 655 | }, 656 | "dependencies": { 657 | "safe-buffer": { 658 | "version": "5.2.1", 659 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 660 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 661 | } 662 | } 663 | }, 664 | "hash.js": { 665 | "version": "1.1.7", 666 | "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", 667 | "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", 668 | "requires": { 669 | "inherits": "^2.0.3", 670 | "minimalistic-assert": "^1.0.1" 671 | } 672 | }, 673 | "hmac-drbg": { 674 | "version": "1.0.1", 675 | "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", 676 | "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", 677 | "requires": { 678 | "hash.js": "^1.0.3", 679 | "minimalistic-assert": "^1.0.0", 680 | "minimalistic-crypto-utils": "^1.0.1" 681 | } 682 | }, 683 | "http-errors": { 684 | "version": "1.7.2", 685 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 686 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 687 | "requires": { 688 | "depd": "~1.1.2", 689 | "inherits": "2.0.3", 690 | "setprototypeof": "1.1.1", 691 | "statuses": ">= 1.5.0 < 2", 692 | "toidentifier": "1.0.0" 693 | }, 694 | "dependencies": { 695 | "inherits": { 696 | "version": "2.0.3", 697 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 698 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 699 | } 700 | } 701 | }, 702 | "iconv-lite": { 703 | "version": "0.4.24", 704 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 705 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 706 | "requires": { 707 | "safer-buffer": ">= 2.1.2 < 3" 708 | } 709 | }, 710 | "inherits": { 711 | "version": "2.0.4", 712 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 713 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 714 | }, 715 | "ip-regex": { 716 | "version": "4.3.0", 717 | "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", 718 | "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==" 719 | }, 720 | "ipaddr.js": { 721 | "version": "1.9.0", 722 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", 723 | "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" 724 | }, 725 | "is-typedarray": { 726 | "version": "1.0.0", 727 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 728 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 729 | }, 730 | "js-sha3": { 731 | "version": "0.8.0", 732 | "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", 733 | "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" 734 | }, 735 | "md5.js": { 736 | "version": "1.3.5", 737 | "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", 738 | "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", 739 | "requires": { 740 | "hash-base": "^3.0.0", 741 | "inherits": "^2.0.1", 742 | "safe-buffer": "^5.1.2" 743 | } 744 | }, 745 | "media-typer": { 746 | "version": "0.3.0", 747 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 748 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 749 | }, 750 | "merge-descriptors": { 751 | "version": "1.0.1", 752 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 753 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 754 | }, 755 | "methods": { 756 | "version": "1.1.2", 757 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 758 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 759 | }, 760 | "mime": { 761 | "version": "1.6.0", 762 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 763 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 764 | }, 765 | "mime-db": { 766 | "version": "1.43.0", 767 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", 768 | "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" 769 | }, 770 | "mime-types": { 771 | "version": "2.1.26", 772 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", 773 | "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", 774 | "requires": { 775 | "mime-db": "1.43.0" 776 | } 777 | }, 778 | "minimalistic-assert": { 779 | "version": "1.0.1", 780 | "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", 781 | "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" 782 | }, 783 | "minimalistic-crypto-utils": { 784 | "version": "1.0.1", 785 | "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", 786 | "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" 787 | }, 788 | "minimist": { 789 | "version": "1.2.5", 790 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 791 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 792 | }, 793 | "mkdirp": { 794 | "version": "0.5.3", 795 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", 796 | "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", 797 | "requires": { 798 | "minimist": "^1.2.5" 799 | } 800 | }, 801 | "ms": { 802 | "version": "2.0.0", 803 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 804 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 805 | }, 806 | "negotiator": { 807 | "version": "0.6.2", 808 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 809 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 810 | }, 811 | "next-tick": { 812 | "version": "1.0.0", 813 | "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", 814 | "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" 815 | }, 816 | "node-fetch": { 817 | "version": "2.6.1", 818 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 819 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 820 | }, 821 | "node-gyp-build": { 822 | "version": "4.2.3", 823 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", 824 | "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==" 825 | }, 826 | "on-finished": { 827 | "version": "2.3.0", 828 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 829 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 830 | "requires": { 831 | "ee-first": "1.1.1" 832 | } 833 | }, 834 | "parseurl": { 835 | "version": "1.3.3", 836 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 837 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 838 | }, 839 | "path-to-regexp": { 840 | "version": "0.1.7", 841 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 842 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 843 | }, 844 | "proxy-addr": { 845 | "version": "2.0.5", 846 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", 847 | "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", 848 | "requires": { 849 | "forwarded": "~0.1.2", 850 | "ipaddr.js": "1.9.0" 851 | } 852 | }, 853 | "qs": { 854 | "version": "6.7.0", 855 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 856 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 857 | }, 858 | "range-parser": { 859 | "version": "1.2.1", 860 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 861 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 862 | }, 863 | "raw-body": { 864 | "version": "2.4.0", 865 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 866 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 867 | "requires": { 868 | "bytes": "3.1.0", 869 | "http-errors": "1.7.2", 870 | "iconv-lite": "0.4.24", 871 | "unpipe": "1.0.0" 872 | } 873 | }, 874 | "readable-stream": { 875 | "version": "3.6.0", 876 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 877 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 878 | "requires": { 879 | "inherits": "^2.0.3", 880 | "string_decoder": "^1.1.1", 881 | "util-deprecate": "^1.0.1" 882 | } 883 | }, 884 | "regenerator-runtime": { 885 | "version": "0.13.9", 886 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", 887 | "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" 888 | }, 889 | "ripemd160": { 890 | "version": "2.0.2", 891 | "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", 892 | "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", 893 | "requires": { 894 | "hash-base": "^3.0.0", 895 | "inherits": "^2.0.1" 896 | } 897 | }, 898 | "rxjs": { 899 | "version": "7.2.0", 900 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.2.0.tgz", 901 | "integrity": "sha512-aX8w9OpKrQmiPKfT1bqETtUr9JygIz6GZ+gql8v7CijClsP0laoFUdKzxFAoWuRdSlOdU2+crss+cMf+cqMTnw==", 902 | "requires": { 903 | "tslib": "~2.1.0" 904 | } 905 | }, 906 | "safe-buffer": { 907 | "version": "5.1.2", 908 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 909 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 910 | }, 911 | "safer-buffer": { 912 | "version": "2.1.2", 913 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 914 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 915 | }, 916 | "scryptsy": { 917 | "version": "2.1.0", 918 | "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", 919 | "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" 920 | }, 921 | "send": { 922 | "version": "0.17.1", 923 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 924 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 925 | "requires": { 926 | "debug": "2.6.9", 927 | "depd": "~1.1.2", 928 | "destroy": "~1.0.4", 929 | "encodeurl": "~1.0.2", 930 | "escape-html": "~1.0.3", 931 | "etag": "~1.8.1", 932 | "fresh": "0.5.2", 933 | "http-errors": "~1.7.2", 934 | "mime": "1.6.0", 935 | "ms": "2.1.1", 936 | "on-finished": "~2.3.0", 937 | "range-parser": "~1.2.1", 938 | "statuses": "~1.5.0" 939 | }, 940 | "dependencies": { 941 | "debug": { 942 | "version": "2.6.9", 943 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 944 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 945 | "requires": { 946 | "ms": "2.0.0" 947 | }, 948 | "dependencies": { 949 | "ms": { 950 | "version": "2.0.0", 951 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 952 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 953 | } 954 | } 955 | }, 956 | "ms": { 957 | "version": "2.1.1", 958 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 959 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 960 | } 961 | } 962 | }, 963 | "serve-static": { 964 | "version": "1.14.1", 965 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 966 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 967 | "requires": { 968 | "encodeurl": "~1.0.2", 969 | "escape-html": "~1.0.3", 970 | "parseurl": "~1.3.3", 971 | "send": "0.17.1" 972 | } 973 | }, 974 | "setprototypeof": { 975 | "version": "1.1.1", 976 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 977 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 978 | }, 979 | "sha.js": { 980 | "version": "2.4.11", 981 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", 982 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", 983 | "requires": { 984 | "inherits": "^2.0.1", 985 | "safe-buffer": "^5.0.1" 986 | } 987 | }, 988 | "statuses": { 989 | "version": "1.5.0", 990 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 991 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 992 | }, 993 | "string_decoder": { 994 | "version": "1.3.0", 995 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 996 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 997 | "requires": { 998 | "safe-buffer": "~5.2.0" 999 | }, 1000 | "dependencies": { 1001 | "safe-buffer": { 1002 | "version": "5.2.1", 1003 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1004 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1005 | } 1006 | } 1007 | }, 1008 | "toidentifier": { 1009 | "version": "1.0.0", 1010 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 1011 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 1012 | }, 1013 | "tslib": { 1014 | "version": "2.1.0", 1015 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", 1016 | "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" 1017 | }, 1018 | "tweetnacl": { 1019 | "version": "1.0.3", 1020 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", 1021 | "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" 1022 | }, 1023 | "type": { 1024 | "version": "1.2.0", 1025 | "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", 1026 | "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" 1027 | }, 1028 | "type-is": { 1029 | "version": "1.6.18", 1030 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1031 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1032 | "requires": { 1033 | "media-typer": "0.3.0", 1034 | "mime-types": "~2.1.24" 1035 | } 1036 | }, 1037 | "typedarray-to-buffer": { 1038 | "version": "3.1.5", 1039 | "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", 1040 | "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", 1041 | "requires": { 1042 | "is-typedarray": "^1.0.0" 1043 | } 1044 | }, 1045 | "unpipe": { 1046 | "version": "1.0.0", 1047 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1048 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1049 | }, 1050 | "utf-8-validate": { 1051 | "version": "5.0.5", 1052 | "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.5.tgz", 1053 | "integrity": "sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ==", 1054 | "requires": { 1055 | "node-gyp-build": "^4.2.0" 1056 | } 1057 | }, 1058 | "util-deprecate": { 1059 | "version": "1.0.2", 1060 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1061 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1062 | }, 1063 | "utils-merge": { 1064 | "version": "1.0.1", 1065 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1066 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1067 | }, 1068 | "vary": { 1069 | "version": "1.1.2", 1070 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1071 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1072 | }, 1073 | "websocket": { 1074 | "version": "1.0.34", 1075 | "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", 1076 | "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", 1077 | "requires": { 1078 | "bufferutil": "^4.0.1", 1079 | "debug": "^2.2.0", 1080 | "es5-ext": "^0.10.50", 1081 | "typedarray-to-buffer": "^3.1.5", 1082 | "utf-8-validate": "^5.0.2", 1083 | "yaeti": "^0.0.6" 1084 | } 1085 | }, 1086 | "xxhashjs": { 1087 | "version": "0.2.2", 1088 | "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", 1089 | "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", 1090 | "requires": { 1091 | "cuint": "^0.2.2" 1092 | } 1093 | }, 1094 | "yaeti": { 1095 | "version": "0.0.6", 1096 | "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", 1097 | "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" 1098 | } 1099 | } 1100 | } 1101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polkadot-api-server", 3 | "version": "1.29.1", 4 | "description": "API Server for Polkadot using WebSockets in JS", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/SimplyVC/polkadot_api_server.git" 8 | }, 9 | "main": "server.js", 10 | "scripts": { 11 | "test": "echo \\\\\\\"Error: no test specified\\\\\\\" && exit 1" 12 | }, 13 | "author": "Daniel Magro", 14 | "license": "Apache-2.0", 15 | "dependencies": { 16 | "@polkadot/api": "^5.1.1", 17 | "@polkadot/api-derive": "^5.1.1", 18 | "await-timeout": "^1.1.1", 19 | "configparser": "^0.3.6", 20 | "express": "^4.17.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /run_api.sh: -------------------------------------------------------------------------------- 1 | cd src/ 2 | node server.js 3 | -------------------------------------------------------------------------------- /run_setup.py: -------------------------------------------------------------------------------- 1 | from configparser import ConfigParser 2 | 3 | from setup import setup_user_config_main, setup_user_config_nodes 4 | 5 | 6 | def run() -> None: 7 | # Initialise parsers 8 | cp_main = ConfigParser() 9 | cp_main.read('config/user_config_main.ini') 10 | cp_nodes = ConfigParser() 11 | cp_nodes.read('config/user_config_nodes.ini') 12 | 13 | # Start setup 14 | print('Welcome to the Polkadot API Server setup script!') 15 | try: 16 | setup_user_config_main.setup_all(cp_main) 17 | with open('config/user_config_main.ini', 'w') as f: 18 | cp_main.write(f) 19 | print('Saved config/user_config_main.ini\n') 20 | 21 | setup_user_config_nodes.setup_nodes(cp_nodes) 22 | with open('config/user_config_nodes.ini', 'w') as f: 23 | cp_nodes.write(f) 24 | print('Saved config/user_config_nodes.ini\n') 25 | 26 | print('Setup completed!') 27 | except KeyboardInterrupt: 28 | print('Setup process stopped.') 29 | return 30 | 31 | 32 | if __name__ == '__main__': 33 | run() 34 | -------------------------------------------------------------------------------- /setup/setup_user_config_main.py: -------------------------------------------------------------------------------- 1 | from configparser import ConfigParser 2 | 3 | from setup.utils.user_input import yn_prompt 4 | 5 | 6 | def is_already_set_up(cp: ConfigParser, section: str) -> bool: 7 | # Find out if the section exists 8 | if section not in cp: 9 | return False 10 | 11 | # Find out if any value in the section (except for enabled) is filled-in 12 | for key in cp[section]: 13 | if cp[section][key] != '': 14 | return True 15 | return False 16 | 17 | 18 | def reset_section(section: str, cp: ConfigParser) -> None: 19 | # Remove and re-add specified section if it exists 20 | if cp.has_section(section): 21 | cp.remove_section(section) 22 | cp.add_section(section) 23 | 24 | 25 | def setup_api_server(cp: ConfigParser) -> None: 26 | print('==== API Server') 27 | print('The API Server makes it possible to query Polkadot Nodes and ' 28 | 'retrieve certain data about the node and the blockchain') 29 | 30 | already_set_up = is_already_set_up(cp, 'api_server') 31 | if already_set_up and \ 32 | not yn_prompt('API Server is already set up. Do you wish ' 33 | 'to clear the current config? (Y/n)\n'): 34 | return 35 | 36 | reset_section('api_server', cp) 37 | cp['api_server']['port'] = '' 38 | 39 | if not already_set_up and \ 40 | not yn_prompt('Do you wish to set up the API Server? (Y/n)\n'): 41 | return 42 | 43 | print('You will now be asked to input the port that will be used by the ' 44 | 'API Server.\nIf you will be running the API Server using Docker, ' 45 | 'you must leave this port as the default.') 46 | port = input('Please insert the port you would like the API Server to use: ' 47 | '(default: 3000)\n') 48 | port = '3000' if port == '' else port 49 | 50 | cp['api_server']['port'] = port 51 | 52 | 53 | def setup_all(cp: ConfigParser) -> None: 54 | setup_api_server(cp) 55 | print() 56 | print('Setup finished.') 57 | -------------------------------------------------------------------------------- /setup/setup_user_config_nodes.py: -------------------------------------------------------------------------------- 1 | from configparser import ConfigParser 2 | from typing import Optional, List 3 | 4 | from setup.utils.config_parsers.user import NodeConfig 5 | from setup.utils.user_input import yn_prompt 6 | 7 | 8 | def get_node(nodes_so_far: List[NodeConfig]) -> Optional[NodeConfig]: 9 | # Get node's name 10 | node_names_so_far = [n.node_name for n in nodes_so_far] 11 | while True: 12 | node_name = input('Unique node name:\n') 13 | if node_name in node_names_so_far: 14 | print('Node name must be unique.') 15 | else: 16 | break 17 | 18 | # Get node's WS url 19 | ws_url = input('Node\'s WS url (typically ws://NODE_IP:9944):\n') 20 | 21 | # Return node 22 | return NodeConfig(node_name, ws_url) 23 | 24 | 25 | def setup_nodes(cp: ConfigParser) -> None: 26 | 27 | print('==== Nodes') 28 | print('To retrieve data from nodes, the API needs to know where to find ' 29 | 'the nodes! The list of nodes the API will connect to will now be ' 30 | 'set up. This includes validators, sentries, and any full nodes that ' 31 | 'can be used as a data source to retrieve data from the network\'s ' 32 | 'perspective. Node names must be unique!') 33 | 34 | # Check if list already set up 35 | already_set_up = len(cp.sections()) > 0 36 | if already_set_up: 37 | if not yn_prompt( 38 | 'The list of nodes is already set up. Do you wish ' 39 | 'to replace this list with a new one? (Y/n)\n'): 40 | return 41 | 42 | # Otherwise ask if they want to set it up 43 | if not already_set_up and \ 44 | not yn_prompt('Do you wish to set up the list of nodes? (Y/n)\n'): 45 | return 46 | 47 | # Clear config and initialise new list 48 | cp.clear() 49 | nodes = [] 50 | 51 | # Get node details and append them to the list of nodes 52 | while True: 53 | node = get_node(nodes) 54 | if node is not None: 55 | nodes.append(node) 56 | print('Successfully added node.') 57 | 58 | if not yn_prompt('Do you want to add another node? (Y/n)\n'): 59 | break 60 | 61 | # Add nodes to config 62 | for i, node in enumerate(nodes): 63 | section = 'node_' + str(i) 64 | cp.add_section(section) 65 | cp[section]['node_name'] = node.node_name 66 | cp[section]['ws_url'] = node.ws_url 67 | -------------------------------------------------------------------------------- /setup/utils/config_parsers/user.py: -------------------------------------------------------------------------------- 1 | class NodeConfig: 2 | 3 | def __init__(self, node_name: str, ws_url: str) -> None: 4 | self.node_name = node_name 5 | self.ws_url = ws_url 6 | -------------------------------------------------------------------------------- /setup/utils/user_input.py: -------------------------------------------------------------------------------- 1 | def prompt(prompt_msg: str) -> str: 2 | # This function was added just for improved testability 3 | return input(prompt_msg) 4 | 5 | 6 | def yn_prompt(prompt_msg: str) -> bool: 7 | while True: 8 | input_str = prompt(prompt_msg) 9 | if input_str.lower() in ['y', '']: 10 | return True 11 | elif input_str.lower() == 'n': 12 | return False 13 | else: 14 | print('Invalid input.') 15 | -------------------------------------------------------------------------------- /src/interface/substrate_derive.js: -------------------------------------------------------------------------------- 1 | const timeoutUtils = require('../utils/timeout'); 2 | 3 | const TIMEOUT_TIME_MS = 10000; 4 | 5 | // Staking 6 | async function getStakingValidators(api) { 7 | return await timeoutUtils.callFnWithTimeoutSafely( 8 | api.derive.staking.validators, [], TIMEOUT_TIME_MS, 9 | 'API call staking/validators failed.' 10 | ); 11 | } 12 | 13 | module.exports = { 14 | deriveAPI: async function (api, param1=null) { 15 | switch (param1) { 16 | // Staking 17 | case 'staking/validators': 18 | try { 19 | return {'result': await getStakingValidators(api)}; 20 | } catch (e) { 21 | return {'error': e.toString()}; 22 | } 23 | default: 24 | if (!param1) { 25 | return {'error': 'You did not enter a method.'}; 26 | } else { 27 | return {'error': 'Invalid API method.'}; 28 | } 29 | } 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/interface/substrate_query.js: -------------------------------------------------------------------------------- 1 | const timeoutUtils = require('../utils/timeout'); 2 | 3 | const TIMEOUT_TIME_MS = 10000; 4 | 5 | // From: https://polkadot.js.org/docs/substrate/storage 6 | // Balances 7 | async function getBalancesTotalIssuance(api) { 8 | return await timeoutUtils.callFnWithTimeoutSafely( 9 | api.query.balances.totalIssuance, [], TIMEOUT_TIME_MS, 10 | 'API call balances/totalIssuance failed.' 11 | ); 12 | } 13 | 14 | // Council 15 | async function getCouncilMembers(api) { 16 | return await timeoutUtils.callFnWithTimeoutSafely( 17 | api.query.council.members, [], TIMEOUT_TIME_MS, 18 | 'API call council/members failed.' 19 | ); 20 | } 21 | 22 | async function getCouncilProposalCount(api) { 23 | return await timeoutUtils.callFnWithTimeoutSafely( 24 | api.query.council.proposalCount, [], TIMEOUT_TIME_MS, 25 | 'API call council/proposalCount failed.' 26 | ); 27 | } 28 | 29 | async function getCouncilProposalOf(api, hash) { 30 | return await timeoutUtils.callFnWithTimeoutSafely( 31 | api.query.council.proposalOf, [hash], TIMEOUT_TIME_MS, 32 | 'API call council/proposalOf failed.' 33 | ); 34 | } 35 | 36 | async function getCouncilProposals(api) { 37 | return await timeoutUtils.callFnWithTimeoutSafely( 38 | api.query.council.proposals, [], TIMEOUT_TIME_MS, 39 | 'API call council/proposals failed.' 40 | ); 41 | } 42 | 43 | // Democracy 44 | async function getDemocracyPublicPropCount(api) { 45 | return await timeoutUtils.callFnWithTimeoutSafely( 46 | api.query.democracy.publicPropCount, [], TIMEOUT_TIME_MS, 47 | 'API call democracy/publicPropCount failed.' 48 | ); 49 | } 50 | 51 | async function getDemocracyReferendumCount(api) { 52 | return await timeoutUtils.callFnWithTimeoutSafely( 53 | api.query.democracy.referendumCount, [], TIMEOUT_TIME_MS, 54 | 'API call democracy/referendumCount failed.' 55 | ); 56 | } 57 | 58 | async function getDemocracyReferendumInfoOf(api, referendumIndex) { 59 | return await timeoutUtils.callFnWithTimeoutSafely( 60 | api.query.democracy.referendumInfoOf, [referendumIndex], 61 | TIMEOUT_TIME_MS, 'API call democracy/referendumInfoOf failed.' 62 | ); 63 | } 64 | 65 | // ImOnline 66 | async function getImOnlineAuthoredBlocks(api, sessionIndex, validatorId) { 67 | return await timeoutUtils.callFnWithTimeoutSafely( 68 | api.query.imOnline.authoredBlocks, [sessionIndex, validatorId], 69 | TIMEOUT_TIME_MS, 'API call imOnline/authoredBlocks failed.' 70 | ); 71 | } 72 | 73 | async function getImOnlineReceivedHeartBeats(api, sessionIndex, authIndex) { 74 | return await timeoutUtils.callFnWithTimeoutSafely( 75 | api.query.imOnline.receivedHeartbeats, [sessionIndex, authIndex], 76 | TIMEOUT_TIME_MS, 'API call imOnline/receivedHeartbeats failed.' 77 | ); 78 | } 79 | 80 | // Session 81 | async function getSessionCurrentIndex(api) { 82 | return await timeoutUtils.callFnWithTimeoutSafely( 83 | api.query.session.currentIndex, [], TIMEOUT_TIME_MS, 84 | 'API call session/currentIndex failed.' 85 | ); 86 | } 87 | 88 | async function getSessionDisabledValidators(api) { 89 | return await timeoutUtils.callFnWithTimeoutSafely( 90 | api.query.session.disabledValidators, [], TIMEOUT_TIME_MS, 91 | 'API call session/disabledValidators failed.' 92 | ); 93 | } 94 | 95 | async function getSessionValidators(api) { 96 | return await timeoutUtils.callFnWithTimeoutSafely( 97 | api.query.session.validators, [], TIMEOUT_TIME_MS, 98 | 'API call session/validators failed.' 99 | ); 100 | } 101 | 102 | // Staking 103 | async function getStakingActiveEra(api) { 104 | return await timeoutUtils.callFnWithTimeoutSafely( 105 | api.query.staking.activeEra, [], TIMEOUT_TIME_MS, 106 | 'API call staking/activeEra failed.' 107 | ); 108 | } 109 | 110 | async function getStakingBonded(api, accountId) { 111 | return await timeoutUtils.callFnWithTimeoutSafely( 112 | api.query.staking.bonded, [accountId], TIMEOUT_TIME_MS, 113 | 'API call staking/bonded failed.' 114 | ); 115 | } 116 | 117 | async function getStakingErasRewardPoints(api, eraIndex) { 118 | if (eraIndex) { 119 | return await timeoutUtils.callFnWithTimeoutSafely( 120 | api.query.staking.erasRewardPoints, [eraIndex], TIMEOUT_TIME_MS, 121 | 'API call staking/erasRewardPoints failed.' 122 | ); 123 | } else { 124 | let activeEraIndex; 125 | try { 126 | activeEraIndex = await getActiveEraIndex(api); 127 | } catch (e) { 128 | console.log('Function call to getActiveEraIndex failed.'); 129 | throw 'API call staking/erasRewardPoints failed.'; 130 | } 131 | return await timeoutUtils.callFnWithTimeoutSafely( 132 | api.query.staking.erasRewardPoints, [activeEraIndex], 133 | TIMEOUT_TIME_MS, 'API call staking/erasRewardPoints failed.' 134 | ); 135 | } 136 | } 137 | 138 | async function getStakingErasStakers(api, accountId, eraIndex) { 139 | // check if eraIndex has been provided or not 140 | if (eraIndex) { 141 | return await timeoutUtils.callFnWithTimeoutSafely( 142 | api.query.staking.erasStakers, [eraIndex, accountId], 143 | TIMEOUT_TIME_MS, 'API call staking/erasStakers failed.' 144 | ); 145 | } else { 146 | let activeEraIndex; 147 | try { 148 | activeEraIndex = await getActiveEraIndex(api); 149 | } catch (e) { 150 | console.log('Function call to getActiveEraIndex failed.'); 151 | throw 'API call staking/erasStakers failed.'; 152 | } 153 | return await timeoutUtils.callFnWithTimeoutSafely( 154 | api.query.staking.erasStakers, [activeEraIndex, accountId], 155 | TIMEOUT_TIME_MS, 'API call staking/erasStakers failed.' 156 | ); 157 | } 158 | } 159 | 160 | async function getStakingErasTotalStake(api, eraIndex) { 161 | // check if eraIndex has been provided or not 162 | if (eraIndex) { 163 | return await timeoutUtils.callFnWithTimeoutSafely( 164 | api.query.staking.erasTotalStake, [eraIndex], TIMEOUT_TIME_MS, 165 | 'API call staking/erasStakers failed.' 166 | ); 167 | } else { 168 | let activeEraIndex; 169 | try { 170 | activeEraIndex = await getActiveEraIndex(api); 171 | } catch (e) { 172 | console.log('Function call to getActiveEraIndex failed.'); 173 | throw 'API call staking/erasTotalStake failed.'; 174 | } 175 | return await timeoutUtils.callFnWithTimeoutSafely( 176 | api.query.staking.erasTotalStake, [activeEraIndex], TIMEOUT_TIME_MS, 177 | 'API call staking/erasTotalStake failed.' 178 | ); 179 | } 180 | } 181 | 182 | async function getStakingErasValidatorReward(api, eraIndex) { 183 | if (eraIndex) { 184 | return await timeoutUtils.callFnWithTimeoutSafely( 185 | api.query.staking.erasValidatorReward, [eraIndex], TIMEOUT_TIME_MS, 186 | 'API call staking/erasValidatorReward failed.' 187 | ); 188 | } else { 189 | let activeEraIndex; 190 | try { 191 | activeEraIndex = await getActiveEraIndex(api); 192 | } catch (e) { 193 | console.log('Function call to getActiveEraIndex failed.'); 194 | throw 'API call staking/erasValidatorReward failed.'; 195 | } 196 | return await timeoutUtils.callFnWithTimeoutSafely( 197 | api.query.staking.erasValidatorReward, [activeEraIndex-1], 198 | TIMEOUT_TIME_MS, 'API call staking/erasValidatorReward failed.' 199 | ); 200 | } 201 | } 202 | 203 | async function getStakingPayee(api, accountId) { 204 | return await timeoutUtils.callFnWithTimeoutSafely( 205 | api.query.staking.payee, [accountId], TIMEOUT_TIME_MS, 206 | 'API call staking/payee failed.' 207 | ); 208 | } 209 | 210 | async function getStakingUnappliedSlashes(api, eraIndex) { 211 | // check if eraIndex has been provided or not 212 | if (eraIndex) { 213 | return await timeoutUtils.callFnWithTimeoutSafely( 214 | api.query.staking.unappliedSlashes, [eraIndex], TIMEOUT_TIME_MS, 215 | 'API call staking/unappliedSlashes failed.' 216 | ); 217 | } else { 218 | let activeEraIndex; 219 | try { 220 | activeEraIndex = await getActiveEraIndex(api); 221 | } catch (e) { 222 | console.log('Function call to getActiveEraIndex failed.'); 223 | throw 'API call staking/unappliedSlashes failed.'; 224 | } 225 | return await timeoutUtils.callFnWithTimeoutSafely( 226 | api.query.staking.unappliedSlashes, [activeEraIndex], 227 | TIMEOUT_TIME_MS, 'API call staking/unappliedSlashes failed.' 228 | ); 229 | } 230 | } 231 | 232 | async function getStakingValidators(api, accountId) { 233 | return await timeoutUtils.callFnWithTimeoutSafely( 234 | api.query.staking.validators, [accountId], TIMEOUT_TIME_MS, 235 | 'API call staking/validators failed.' 236 | ); 237 | } 238 | 239 | // System 240 | async function getSystemEvents(api, blockHash) { 241 | // check if blockHash has been provided or not 242 | if (blockHash) { 243 | return await timeoutUtils.callFnWithTimeoutSafely( 244 | api.query.system.events.at, [blockHash], TIMEOUT_TIME_MS, 245 | 'API call system/events failed.' 246 | ); 247 | } else { 248 | return await timeoutUtils.callFnWithTimeoutSafely( 249 | api.query.system.events, [], TIMEOUT_TIME_MS, 250 | 'API call system/events failed.' 251 | ); 252 | } 253 | } 254 | 255 | // Custom 256 | async function getActiveEraIndex(api) { 257 | let activeEra = await timeoutUtils.callFnWithTimeoutSafely( 258 | getStakingActiveEra, [api], TIMEOUT_TIME_MS, 259 | 'API call custom/getActiveEraIndex failed.' 260 | ); 261 | try { 262 | return activeEra.toJSON()['index']; 263 | } catch (e) { 264 | throw 'API call custom/getActiveEraIndex failed.'; 265 | } 266 | } 267 | 268 | async function getSlashAmount(api, blockHash, accountAddress) { 269 | let events; 270 | try { 271 | events = await getSystemEvents(api, blockHash); 272 | } catch (e) { 273 | throw 'API call custom/getSlashAmount failed.'; 274 | } 275 | let slashAmount = 0; 276 | // check every event to look for a staking:Slash event 277 | for (const record of events) { 278 | // extract the event and the event types 279 | const event = record.event; 280 | // check if the current event is a staking:Slash event 281 | if (event.section == 'staking' && event.method == 'Slash') { 282 | const event_str = event.data.toString(); 283 | // remove the [ and ] from the string. 284 | // split the string into two elements by the ',' 285 | const event_arr = event_str.slice(1, event_str.length-1).split(','); 286 | // remove the " from the ends of the account id 287 | event_arr[0] = event_arr[0].slice(1,event_arr[0].length-1); 288 | 289 | // check if the accountAddress of the current slashing event is the 290 | // one being queried 291 | if (event_arr[0] == accountAddress) { 292 | // if it is, add the slashed amount in the event to the total 293 | slashAmount += parseInt(event_arr[1]); 294 | } 295 | } 296 | } 297 | return slashAmount; 298 | } 299 | 300 | 301 | module.exports = { 302 | queryAPI: async function (api, param1=null, param2=null, param3=null) { 303 | switch (param1) { 304 | // Balances 305 | case 'balances/totalIssuance': 306 | try { 307 | return {'result': await getBalancesTotalIssuance(api)}; 308 | } catch (e) { 309 | return {'error': e.toString()}; 310 | } 311 | // Council 312 | case 'council/members': 313 | try { 314 | return {'result': await getCouncilMembers(api)}; 315 | } catch (e) { 316 | return {'error': e.toString()}; 317 | } 318 | case 'council/proposalCount': 319 | try { 320 | return {'result': await getCouncilProposalCount(api)}; 321 | } catch (e) { 322 | return {'error': e.toString()}; 323 | } 324 | case 'council/proposalOf': 325 | if (!param2) { 326 | return {'error': 'You did not enter the hash.'}; 327 | } 328 | try { 329 | return {'result': await getCouncilProposalOf(api, param2)}; 330 | } catch (e) { 331 | return {'error': e.toString()}; 332 | } 333 | case 'council/proposals': 334 | try { 335 | return {'result': await getCouncilProposals(api)}; 336 | } catch (e) { 337 | return {'error': e.toString()}; 338 | } 339 | // Democracy 340 | case 'democracy/publicPropCount': 341 | try { 342 | return {'result': await getDemocracyPublicPropCount(api)}; 343 | } catch (e) { 344 | return {'error': e.toString()}; 345 | } 346 | case 'democracy/referendumCount': 347 | try { 348 | return {'result': await getDemocracyReferendumCount(api)}; 349 | } catch (e) { 350 | return {'error': e.toString()}; 351 | } 352 | case 'democracy/referendumInfoOf': 353 | if (!param2) { 354 | return {'error': 'You did not enter the referendum index.'}; 355 | } 356 | try { 357 | return {'result': await getDemocracyReferendumInfoOf(api, 358 | param2)}; 359 | } catch (e) { 360 | return {'error': e.toString()}; 361 | } 362 | // ImOnline 363 | case 'imOnline/authoredBlocks': 364 | if (!param2) { 365 | return {'error': 'You did not enter the session index.'}; 366 | } 367 | if (!param3){ 368 | return {'error': 'You did not enter the stash account ' 369 | + 'address of the validator that needs to be ' 370 | + 'queried'}; 371 | } 372 | try { 373 | return {'result': await getImOnlineAuthoredBlocks(api, 374 | param2, param3)}; 375 | } catch (e) { 376 | return {'error': e.toString()}; 377 | } 378 | case 'imOnline/receivedHeartbeats': 379 | if (!param2) { 380 | return {'error': 'You did not enter the session index.'}; 381 | } 382 | if (!param3){ 383 | return {'error': 'You did not enter the index of the ' + 384 | 'validator in the list returned by ' + 385 | 'session.validators()'}; 386 | } 387 | try { 388 | return {'result': await getImOnlineReceivedHeartBeats(api, 389 | param2, param3)}; 390 | } catch (e) { 391 | return {'error': e.toString()}; 392 | } 393 | // Session 394 | case 'session/currentIndex': 395 | try { 396 | return {'result': await getSessionCurrentIndex(api)}; 397 | } catch (e) { 398 | return {'error': e.toString()}; 399 | } 400 | case 'session/disabledValidators': 401 | try { 402 | return {'result': await getSessionDisabledValidators(api)}; 403 | } catch (e) { 404 | return {'error': e.toString()}; 405 | } 406 | case 'session/validators': 407 | try { 408 | return {'result': await getSessionValidators(api)}; 409 | } catch (e) { 410 | return {'error': e.toString()}; 411 | } 412 | // Staking 413 | case 'staking/activeEra': 414 | try { 415 | return {'result': await getStakingActiveEra(api)}; 416 | } catch (e) { 417 | return {'error': e.toString()}; 418 | } 419 | case 'staking/bonded': 420 | if (!param2) { 421 | return {'error': 'You did not enter the stash ' 422 | + 'address that needs to be queried'}; 423 | } 424 | try { 425 | return {'result': await getStakingBonded(api, param2)}; 426 | } catch (e) { 427 | return {'error': e.toString()}; 428 | } 429 | case 'staking/erasRewardPoints': 430 | try { 431 | return {'result': await getStakingErasRewardPoints(api, 432 | param2)}; 433 | } catch (e) { 434 | return {'error': e.toString()}; 435 | } 436 | case 'staking/erasStakers': 437 | if (!param2) { 438 | return {'error': 'You did not enter the stash account ' 439 | + 'address of the validator that needs to be ' 440 | + 'queried'}; 441 | } 442 | try { 443 | return {'result': await getStakingErasStakers(api, param2, 444 | param3)}; 445 | } catch (e) { 446 | return {'error': e.toString()}; 447 | } 448 | case 'staking/erasTotalStake': 449 | try { 450 | return {'result': await getStakingErasTotalStake(api, 451 | param2)}; 452 | } catch (e) { 453 | return {'error': e.toString()}; 454 | } 455 | case 'staking/erasValidatorReward': 456 | try { 457 | return {'result': await getStakingErasValidatorReward(api, 458 | param2)}; 459 | } catch (e) { 460 | return {'error': e.toString()}; 461 | } 462 | case 'staking/payee': 463 | if (!param2) { 464 | return {'error': 'You did not enter the stash ' 465 | + 'address that needs to be queried'}; 466 | } 467 | try { 468 | return {'result': await getStakingPayee(api, param2)}; 469 | } catch (e) { 470 | return {'error': e.toString()}; 471 | } 472 | case 'staking/unappliedSlashes': 473 | try { 474 | return {'result': await getStakingUnappliedSlashes(api, 475 | param2)}; 476 | } catch (e) { 477 | return {'error': e.toString()}; 478 | } 479 | case 'staking/validators': 480 | if (!param2) { 481 | return {'error': 'You did not enter the stash ' 482 | + 'address that needs to be queried'}; 483 | } 484 | try{ 485 | return {'result': await getStakingValidators(api, param2)}; 486 | } catch (e) { 487 | return {'error': e.toString()}; 488 | } 489 | // System 490 | case 'system/events': 491 | try { 492 | return {'result': await getSystemEvents(api, param2)}; 493 | } catch (e) { 494 | return {'error': e.toString()}; 495 | } 496 | // Custom Endpoints 497 | case 'custom/getSlashAmount': 498 | try { 499 | if (!param3) { 500 | return {'error': 'You did not enter the account ' 501 | + 'address of the validator that needs to be ' 502 | + 'queried'}; 503 | } 504 | return {'result': await getSlashAmount(api, param2, 505 | param3)}; 506 | } catch (e) { 507 | return {'error': e.toString()}; 508 | } 509 | default: 510 | if (!param1) { 511 | return {'error': 'You did not enter a method.'}; 512 | } else { 513 | return {'error': 'Invalid API method.'}; 514 | } 515 | } 516 | } 517 | }; 518 | -------------------------------------------------------------------------------- /src/interface/substrate_rpc.js: -------------------------------------------------------------------------------- 1 | const timeoutUtils = require('../utils/timeout'); 2 | 3 | const TIMEOUT_TIME_MS = 10000; 4 | 5 | // From: https://polkadot.js.org/docs/substrate/rpc 6 | // Chain 7 | async function getChainGetBlockHash(api, blockNumber){ 8 | // check if blockNumber has been provided or not 9 | if (blockNumber) { 10 | return await timeoutUtils.callFnWithTimeoutSafely( 11 | api.rpc.chain.getBlockHash, [blockNumber], TIMEOUT_TIME_MS, 12 | 'API call chain/getBlockHash failed.' 13 | ); 14 | } else { 15 | return await timeoutUtils.callFnWithTimeoutSafely( 16 | api.rpc.chain.getBlockHash, [], TIMEOUT_TIME_MS, 17 | 'API call chain/getBlockHash failed.' 18 | ); 19 | } 20 | } 21 | 22 | async function getChainGetFinalizedHead(api){ 23 | return await timeoutUtils.callFnWithTimeoutSafely( 24 | api.rpc.chain.getFinalizedHead, [], TIMEOUT_TIME_MS, 25 | 'API call chain/getFinalizedHead failed.' 26 | ); 27 | } 28 | 29 | async function getChainGetHeader(api, hash){ 30 | // check if hash has been provided or not 31 | if (hash) { 32 | return await timeoutUtils.callFnWithTimeoutSafely( 33 | api.rpc.chain.getHeader, [hash], TIMEOUT_TIME_MS, 34 | 'API call chain/getHeader failed.' 35 | ); 36 | } else { 37 | return await timeoutUtils.callFnWithTimeoutSafely( 38 | api.rpc.chain.getHeader, [], TIMEOUT_TIME_MS, 39 | 'API call chain/getHeader failed.' 40 | ); 41 | } 42 | } 43 | 44 | // RPC 45 | async function getRPCMethods(api){ 46 | return await timeoutUtils.callFnWithTimeoutSafely( 47 | api.rpc.rpc.methods, [], TIMEOUT_TIME_MS, 'API call rpc/methods failed.' 48 | ); 49 | } 50 | 51 | // System 52 | async function getSystemChain(api){ 53 | return await timeoutUtils.callFnWithTimeoutSafely( 54 | api.rpc.system.chain, [], TIMEOUT_TIME_MS, 55 | 'API call system/chain failed.' 56 | ); 57 | } 58 | 59 | async function getSystemHealth(api){ 60 | return await timeoutUtils.callFnWithTimeoutSafely( 61 | api.rpc.system.health, [], TIMEOUT_TIME_MS, 62 | 'API call system/health failed.' 63 | ); 64 | } 65 | 66 | async function getSystemNetworkState(api){ 67 | return await timeoutUtils.callFnWithTimeoutSafely( 68 | api.rpc.system.networkState, [], TIMEOUT_TIME_MS, 69 | 'API call system/networkState failed.' 70 | ); 71 | } 72 | 73 | async function getSystemProperties(api){ 74 | return await timeoutUtils.callFnWithTimeoutSafely( 75 | api.rpc.system.properties, [], TIMEOUT_TIME_MS, 76 | 'API call system/properties failed.' 77 | ); 78 | } 79 | 80 | 81 | module.exports = { 82 | rpcAPI: async function (api, param1=null, param2=null) { 83 | switch (param1) { 84 | // Chain 85 | case 'chain/getBlockHash': 86 | try { 87 | return {'result': await getChainGetBlockHash(api, param2)}; 88 | } catch (e) { 89 | return {'error': e.toString()} 90 | } 91 | case 'chain/getFinalizedHead': 92 | try { 93 | return {'result': await getChainGetFinalizedHead(api)}; 94 | } catch (e) { 95 | return {'error': e.toString()} 96 | } 97 | case 'chain/getHeader': 98 | try { 99 | return {'result': await getChainGetHeader(api, param2)}; 100 | } catch (e) { 101 | return {'error': e.toString()} 102 | } 103 | // RPC 104 | case 'rpc/methods': 105 | try { 106 | return {'result': await getRPCMethods(api)}; 107 | } catch (e) { 108 | return {'error': e.toString()} 109 | } 110 | // System 111 | case 'system/chain': 112 | try { 113 | return {'result': await getSystemChain(api)} 114 | } catch (e) { 115 | return {'error': e.toString()}; 116 | } 117 | case 'system/health': 118 | try { 119 | return {'result': await getSystemHealth(api)}; 120 | } catch (e) { 121 | return {'error': e.toString()} 122 | } 123 | case 'system/networkState': 124 | try { 125 | return {'result': await getSystemNetworkState(api)}; 126 | } catch (e) { 127 | return {'error': e.toString()} 128 | } 129 | case 'system/properties': 130 | try { 131 | return {'result': await getSystemProperties(api)}; 132 | } catch (e) { 133 | return {'error': e.toString()} 134 | } 135 | default: 136 | if (!param1) { 137 | return {'error': "You did not enter a method."}; 138 | } else { 139 | return {'error': "Invalid API method."}; 140 | } 141 | } 142 | } 143 | }; 144 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const ConfigParser = require('configparser'); 3 | const {ApiPromise, WsProvider} = require('@polkadot/api'); 4 | 5 | const substrateRPC = require('./interface/substrate_rpc'); 6 | const substrateQuery = require('./interface/substrate_query'); 7 | const substrateDerive = require('./interface/substrate_derive'); 8 | const timeoutUtils = require('./utils/timeout'); 9 | 10 | 11 | const TIMEOUT_TIME_MS = 10000; 12 | const REQUEST_SUCCESS_STATUS = 200; 13 | const REQUEST_ERROR_STATUS = 400; 14 | 15 | 16 | function errorNeedToSetUpAPIMsg(websocket) { 17 | return {'error': `An API for ${websocket} ` 18 | + 'needs to be setup before it can be queried'} 19 | } 20 | 21 | async function startListen(websocket) { 22 | try { 23 | console.log(`Connecting to ${websocket}`); 24 | // connect to the WebSocket once and reuse that connection 25 | let provider = new WsProvider(websocket); 26 | // open an API connection with the provider 27 | let api = await timeoutUtils.callFnWithTimeoutSafely( 28 | ApiPromise.create, [{provider}], TIMEOUT_TIME_MS, 29 | 'Connection could not be established.' 30 | ); 31 | return {api: api, provider: provider}; 32 | } catch (e) { 33 | console.log(e.toString()); 34 | } 35 | } 36 | 37 | async function startPolkadotAPI() { 38 | // Start up the API 39 | const app = express(); 40 | await app.use(express.json()); 41 | 42 | // declare a dictionary which will contain all the apis and providers that 43 | // correspond to the IPs submitted by the user in the format 44 | // websocket_ip -> { api, provider } 45 | let apiProviderDict = {}; 46 | 47 | // Read the configuration file (user_config_main.ini) 48 | // create a ConfigParser object which can read the config file 49 | const user_config_main = new ConfigParser(); 50 | // specify the config file from which to read the configuration 51 | user_config_main.read('../config/user_config_main.ini'); 52 | 53 | // read which API Port to use from the main config 54 | const API_PORT = await user_config_main.get('api_server', 'port'); 55 | 56 | // Read the configuration file (user_config_nodes.ini) 57 | // create a ConfigParser object which can read the config file 58 | const user_config_nodes = new ConfigParser(); 59 | // specify the config file from which to read the configuration 60 | user_config_nodes.read('../config/user_config_nodes.ini'); 61 | 62 | // iterate over every node defined in the nodes config 63 | for (let i = 0; i < user_config_nodes.sections().length; i++) { 64 | // read the node's websocket from the config file 65 | const websocket = user_config_nodes.get( 66 | user_config_nodes.sections()[i], 'ws_url'); 67 | 68 | // check whether an api has already been connected for that websocket_ip 69 | if (websocket in apiProviderDict) { 70 | console.log(`An API for ${websocket} has already been set up`); 71 | } else { 72 | // add the api and provider to the dictionary apiProviderDict so 73 | // that it can be accessed using its websocket ip as its key 74 | apiProviderDict[websocket] = await startListen(websocket); 75 | // check if an API connection was successfully established 76 | if (apiProviderDict.hasOwnProperty(websocket) 77 | && apiProviderDict[websocket]) { 78 | console.log(`Successfully Connected to ${websocket}`); 79 | } 80 | } 81 | } 82 | 83 | await app.listen(API_PORT); 84 | console.log(`API running on port ${API_PORT}`); 85 | 86 | // Miscellaneous Endpoints 87 | app.get('/api/pingApi', async function (req, res) { 88 | console.log('Received request for %s', req.url); 89 | try { 90 | return res.status(REQUEST_SUCCESS_STATUS).send({'result': 'pong'}); 91 | } catch (e) { 92 | return res.status(REQUEST_ERROR_STATUS).send( 93 | {'error': e.toString()}); 94 | } 95 | }); 96 | 97 | app.get('/api/pingNode', async function (req, res) { 98 | console.log('Received request for %s', req.url); 99 | try { 100 | // extract the web socket passed in the query 101 | const websocket = req.query.websocket; 102 | // check whether an api has been connected for that websocket 103 | if (websocket in apiProviderDict) { 104 | const apiResult = await substrateRPC.rpcAPI( 105 | apiProviderDict[websocket].api, "system/chain" 106 | ); 107 | if ('result' in apiResult) { 108 | return res.status(REQUEST_SUCCESS_STATUS).send( 109 | {'result': 'pong'}); 110 | } else { 111 | if (apiProviderDict[websocket].provider.isConnected) { 112 | return res.status(REQUEST_ERROR_STATUS).send( 113 | {'error': 'API call pingNode failed.'}); 114 | } else { 115 | return res.status(REQUEST_ERROR_STATUS).send( 116 | {'error': 'Lost connection with node.'}); 117 | } 118 | } 119 | } else { 120 | return res.status(REQUEST_ERROR_STATUS).send( 121 | errorNeedToSetUpAPIMsg(websocket)) 122 | } 123 | } catch (e) { 124 | return res.status(REQUEST_ERROR_STATUS).send( 125 | {'error': e.toString()}); 126 | } 127 | }); 128 | 129 | app.get('/api/getConnectionsList', async function (req, res) { 130 | console.log('Received request for %s', req.url); 131 | try { 132 | let websocket_api_list = []; 133 | // iterate over each key in the map of websocket_ip -> 134 | // {api, provider} 135 | for (let websocket_ip in apiProviderDict) { 136 | // check if API is defined (so it is not returned in the list 137 | // if a connection was never established) 138 | if (apiProviderDict.hasOwnProperty(websocket_ip) 139 | && apiProviderDict[websocket_ip]) { 140 | // add the ip to the list of ips 141 | websocket_api_list.push(websocket_ip) 142 | } 143 | } 144 | return res.status(REQUEST_SUCCESS_STATUS).send( 145 | {'result': websocket_api_list}); 146 | } catch (e) { 147 | return res.status(REQUEST_ERROR_STATUS).send( 148 | {'error': e.toString()}); 149 | } 150 | }); 151 | 152 | // RPC API Endpoints 153 | // Chain 154 | app.get('/api/rpc/chain/getBlockHash', async function (req, res) { 155 | console.log('Received request for %s', req.url); 156 | try { 157 | // extract the web socket passed in the query 158 | const websocket = req.query.websocket; 159 | // extract the blockNumber passed in the query (optional) 160 | const blockNumber = req.query.block_number; 161 | // check whether an api has been connected for that websocket 162 | if (websocket in apiProviderDict) { 163 | const apiResult = await substrateRPC.rpcAPI( 164 | apiProviderDict[websocket].api, "chain/getBlockHash", 165 | blockNumber 166 | ); 167 | if ('result' in apiResult) { 168 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 169 | } else { 170 | if (apiProviderDict[websocket].provider.isConnected) { 171 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 172 | } else { 173 | return res.status(REQUEST_ERROR_STATUS).send( 174 | {'error': 'Lost connection with node.'}); 175 | } 176 | } 177 | } else { 178 | return res.status(REQUEST_ERROR_STATUS).send( 179 | errorNeedToSetUpAPIMsg(websocket)) 180 | } 181 | } catch (e) { 182 | return res.status(REQUEST_ERROR_STATUS).send( 183 | {'error': e.toString()}); 184 | } 185 | }); 186 | 187 | app.get('/api/rpc/chain/getFinalizedHead', async function (req, res) { 188 | console.log('Received request for %s', req.url); 189 | try { 190 | // extract the web socket passed in the query 191 | const websocket = req.query.websocket; 192 | // check whether an api has been connected for that websocket 193 | if (websocket in apiProviderDict) { 194 | const apiResult = await substrateRPC.rpcAPI( 195 | apiProviderDict[websocket].api, "chain/getFinalizedHead"); 196 | if ('result' in apiResult) { 197 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 198 | } else { 199 | if (apiProviderDict[websocket].provider.isConnected) { 200 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 201 | } else { 202 | return res.status(REQUEST_ERROR_STATUS).send( 203 | {'error': 'Lost connection with node.'}); 204 | } 205 | } 206 | } else { 207 | return res.status(REQUEST_ERROR_STATUS).send( 208 | errorNeedToSetUpAPIMsg(websocket)) 209 | } 210 | } catch (e) { 211 | return res.status(REQUEST_ERROR_STATUS).send( 212 | {'error': e.toString()}); 213 | } 214 | }); 215 | 216 | app.get('/api/rpc/chain/getHeader', async function (req, res) { 217 | console.log('Received request for %s', req.url); 218 | try { 219 | // extract the web socket passed in the query 220 | const websocket = req.query.websocket; 221 | // extract the hash passed in the query (optional) 222 | const hash = req.query.hash; 223 | // check whether an api has been connected for that websocket 224 | if (websocket in apiProviderDict) { 225 | const apiResult = await substrateRPC.rpcAPI( 226 | apiProviderDict[websocket].api, "chain/getHeader", hash); 227 | if ('result' in apiResult) { 228 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 229 | } else { 230 | if (apiProviderDict[websocket].provider.isConnected) { 231 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 232 | } else { 233 | return res.status(REQUEST_ERROR_STATUS).send( 234 | {'error': 'Lost connection with node.'}); 235 | } 236 | } 237 | } else { 238 | return res.status(REQUEST_ERROR_STATUS).send( 239 | errorNeedToSetUpAPIMsg(websocket)) 240 | } 241 | } catch (e) { 242 | return res.status(REQUEST_ERROR_STATUS).send( 243 | {'error': e.toString()}); 244 | } 245 | }); 246 | 247 | // RPC 248 | app.get('/api/rpc/rpc/methods', async function (req, res) { 249 | console.log('Received request for %s', req.url); 250 | try { 251 | // extract the web socket passed in the query 252 | const websocket = req.query.websocket; 253 | // check whether an api has been connected for that websocket 254 | if (websocket in apiProviderDict) { 255 | const apiResult = await substrateRPC.rpcAPI( 256 | apiProviderDict[websocket].api, "rpc/methods"); 257 | if ('result' in apiResult) { 258 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 259 | } else { 260 | if (apiProviderDict[websocket].provider.isConnected) { 261 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 262 | } else { 263 | return res.status(REQUEST_ERROR_STATUS).send( 264 | {'error': 'Lost connection with node.'}); 265 | } 266 | } 267 | } else { 268 | return res.status(REQUEST_ERROR_STATUS).send( 269 | errorNeedToSetUpAPIMsg(websocket)) 270 | } 271 | } catch (e) { 272 | return res.status(REQUEST_ERROR_STATUS).send( 273 | {'error': e.toString()}); 274 | } 275 | }); 276 | 277 | // System 278 | app.get('/api/rpc/system/chain', async function (req, res) { 279 | console.log('Received request for %s', req.url); 280 | try { 281 | // extract the web socket passed in the query 282 | const websocket = req.query.websocket; 283 | // check whether an api has been connected for that websocket 284 | if (websocket in apiProviderDict) { 285 | const apiResult = await substrateRPC.rpcAPI( 286 | apiProviderDict[websocket].api, "system/chain"); 287 | if ('result' in apiResult) { 288 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 289 | } else { 290 | if (apiProviderDict[websocket].provider.isConnected) { 291 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 292 | } else { 293 | return res.status(REQUEST_ERROR_STATUS).send( 294 | {'error': 'Lost connection with node.'}); 295 | } 296 | } 297 | } else { 298 | return res.status(REQUEST_ERROR_STATUS).send( 299 | errorNeedToSetUpAPIMsg(websocket)) 300 | } 301 | } catch (e) { 302 | return res.status(REQUEST_ERROR_STATUS).send( 303 | {'error': e.toString()}); 304 | } 305 | }); 306 | 307 | app.get('/api/rpc/system/health', async function (req, res) { 308 | console.log('Received request for %s', req.url); 309 | try { 310 | // extract the web socket passed in the query 311 | const websocket = req.query.websocket; 312 | // check whether an api has been connected for that websocket 313 | if (websocket in apiProviderDict) { 314 | const apiResult = await substrateRPC.rpcAPI( 315 | apiProviderDict[websocket].api, "system/health"); 316 | if ('result' in apiResult) { 317 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 318 | } else { 319 | if (apiProviderDict[websocket].provider.isConnected) { 320 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 321 | } else { 322 | return res.status(REQUEST_ERROR_STATUS).send( 323 | {'error': 'Lost connection with node.'}); 324 | } 325 | } 326 | } else { 327 | return res.status(REQUEST_ERROR_STATUS).send( 328 | errorNeedToSetUpAPIMsg(websocket)) 329 | } 330 | } catch (e) { 331 | return res.status(REQUEST_ERROR_STATUS).send( 332 | {'error': e.toString()}); 333 | } 334 | }); 335 | 336 | app.get('/api/rpc/system/networkState', async function (req, res) { 337 | console.log('Received request for %s', req.url); 338 | try { 339 | // extract the web socket passed in the query 340 | const websocket = req.query.websocket; 341 | // check whether an api has been connected for that websocket 342 | if (websocket in apiProviderDict) { 343 | const apiResult = await substrateRPC.rpcAPI( 344 | apiProviderDict[websocket].api, "system/networkState"); 345 | if ('result' in apiResult) { 346 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 347 | } else { 348 | if (apiProviderDict[websocket].provider.isConnected) { 349 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 350 | } else { 351 | return res.status(REQUEST_ERROR_STATUS).send( 352 | {'error': 'Lost connection with node.'}); 353 | } 354 | } 355 | } else { 356 | return res.status(REQUEST_ERROR_STATUS).send( 357 | errorNeedToSetUpAPIMsg(websocket)) 358 | } 359 | } catch (e) { 360 | return res.status(REQUEST_ERROR_STATUS).send( 361 | {'error': e.toString()}); 362 | } 363 | }); 364 | 365 | app.get('/api/rpc/system/properties', async function (req, res) { 366 | console.log('Received request for %s', req.url); 367 | try { 368 | // extract the web socket passed in the query 369 | const websocket = req.query.websocket; 370 | // check whether an api has been connected for that websocket 371 | if (websocket in apiProviderDict) { 372 | const apiResult = await substrateRPC.rpcAPI( 373 | apiProviderDict[websocket].api, "system/properties"); 374 | if ('result' in apiResult) { 375 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 376 | } else { 377 | if (apiProviderDict[websocket].provider.isConnected) { 378 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 379 | } else { 380 | return res.status(REQUEST_ERROR_STATUS).send( 381 | {'error': 'Lost connection with node.'}); 382 | } 383 | } 384 | } else { 385 | return res.status(REQUEST_ERROR_STATUS).send( 386 | errorNeedToSetUpAPIMsg(websocket)) 387 | } 388 | } catch (e) { 389 | return res.status(REQUEST_ERROR_STATUS).send( 390 | {'error': e.toString()}); 391 | } 392 | }); 393 | 394 | // Query API Endpoints 395 | // Balances 396 | app.get('/api/query/balances/totalIssuance', async function (req, res) { 397 | console.log('Received request for %s', req.url); 398 | try { 399 | // extract the web socket passed in the query 400 | const websocket = req.query.websocket; 401 | // check whether an api has been connected for that websocket 402 | if (websocket in apiProviderDict) { 403 | const apiResult = await substrateQuery.queryAPI( 404 | apiProviderDict[websocket].api, "balances/totalIssuance"); 405 | if ('result' in apiResult) { 406 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 407 | } else { 408 | if (apiProviderDict[websocket].provider.isConnected) { 409 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 410 | } else { 411 | return res.status(REQUEST_ERROR_STATUS).send( 412 | {'error': 'Lost connection with node.'}); 413 | } 414 | } 415 | } else { 416 | return res.status(REQUEST_ERROR_STATUS).send( 417 | errorNeedToSetUpAPIMsg(websocket)) 418 | } 419 | } catch (e) { 420 | return res.status(REQUEST_ERROR_STATUS).send( 421 | {'error': e.toString()}); 422 | } 423 | }); 424 | 425 | // Council 426 | app.get('/api/query/council/members', async function (req, res) { 427 | console.log('Received request for %s', req.url); 428 | try { 429 | // extract the web socket passed in the query 430 | const websocket = req.query.websocket; 431 | // check whether an api has been connected for that websocket 432 | if (websocket in apiProviderDict) { 433 | const apiResult = await substrateQuery.queryAPI( 434 | apiProviderDict[websocket].api, "council/members"); 435 | if ('result' in apiResult) { 436 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 437 | } else { 438 | if (apiProviderDict[websocket].provider.isConnected) { 439 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 440 | } else { 441 | return res.status(REQUEST_ERROR_STATUS).send( 442 | {'error': 'Lost connection with node.'}); 443 | } 444 | } 445 | } else { 446 | return res.status(REQUEST_ERROR_STATUS).send( 447 | errorNeedToSetUpAPIMsg(websocket)) 448 | } 449 | } catch (e) { 450 | return res.status(REQUEST_ERROR_STATUS).send( 451 | {'error': e.toString()}); 452 | } 453 | }); 454 | 455 | app.get('/api/query/council/proposalCount', async function (req, res) { 456 | console.log('Received request for %s', req.url); 457 | try { 458 | // extract the web socket passed in the query 459 | const websocket = req.query.websocket; 460 | // check whether an api has been connected for that websocket 461 | if (websocket in apiProviderDict) { 462 | const apiResult = await substrateQuery.queryAPI( 463 | apiProviderDict[websocket].api, "council/proposalCount"); 464 | if ('result' in apiResult) { 465 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 466 | } else { 467 | if (apiProviderDict[websocket].provider.isConnected) { 468 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 469 | } else { 470 | return res.status(REQUEST_ERROR_STATUS).send( 471 | {'error': 'Lost connection with node.'}); 472 | } 473 | } 474 | } else { 475 | return res.status(REQUEST_ERROR_STATUS).send( 476 | errorNeedToSetUpAPIMsg(websocket)) 477 | } 478 | } catch (e) { 479 | return res.status(REQUEST_ERROR_STATUS).send( 480 | {'error': e.toString()}); 481 | } 482 | }); 483 | 484 | app.get('/api/query/council/proposalOf', async function (req, res) { 485 | console.log('Received request for %s', req.url); 486 | try { 487 | // extract the web socket passed in the query 488 | const websocket = req.query.websocket; 489 | // extract the hash passed in the query 490 | const hash = req.query.hash; 491 | // check whether an api has been connected for that websocket 492 | if (websocket in apiProviderDict) { 493 | const apiResult = await substrateQuery.queryAPI( 494 | apiProviderDict[websocket].api, "council/proposalOf", hash); 495 | if ('result' in apiResult) { 496 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 497 | } else { 498 | if (apiProviderDict[websocket].provider.isConnected) { 499 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 500 | } else { 501 | return res.status(REQUEST_ERROR_STATUS).send( 502 | {'error': 'Lost connection with node.'}); 503 | } 504 | } 505 | } else { 506 | return res.status(REQUEST_ERROR_STATUS).send( 507 | errorNeedToSetUpAPIMsg(websocket)) 508 | } 509 | } catch (e) { 510 | return res.status(REQUEST_ERROR_STATUS).send( 511 | {'error': e.toString()}); 512 | } 513 | }); 514 | 515 | app.get('/api/query/council/proposals', async function (req, res) { 516 | console.log('Received request for %s', req.url); 517 | try { 518 | // extract the web socket passed in the query 519 | const websocket = req.query.websocket; 520 | // check whether an api has been connected for that websocket 521 | if (websocket in apiProviderDict) { 522 | const apiResult = await substrateQuery.queryAPI( 523 | apiProviderDict[websocket].api, "council/proposals"); 524 | if ('result' in apiResult) { 525 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 526 | } else { 527 | if (apiProviderDict[websocket].provider.isConnected) { 528 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 529 | } else { 530 | return res.status(REQUEST_ERROR_STATUS).send( 531 | {'error': 'Lost connection with node.'}); 532 | } 533 | } 534 | } else { 535 | return res.status(REQUEST_ERROR_STATUS).send( 536 | errorNeedToSetUpAPIMsg(websocket)) 537 | } 538 | } catch (e) { 539 | return res.status(REQUEST_ERROR_STATUS).send( 540 | {'error': e.toString()}); 541 | } 542 | }); 543 | 544 | // Democracy 545 | app.get('/api/query/democracy/publicPropCount', async function (req, res) { 546 | console.log('Received request for %s', req.url); 547 | try { 548 | // extract the web socket passed in the query 549 | const websocket = req.query.websocket; 550 | // check whether an api has been connected for that websocket 551 | if (websocket in apiProviderDict) { 552 | const apiResult = await substrateQuery.queryAPI( 553 | apiProviderDict[websocket].api, "democracy/publicPropCount" 554 | ); 555 | if ('result' in apiResult) { 556 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 557 | } else { 558 | if (apiProviderDict[websocket].provider.isConnected) { 559 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 560 | } else { 561 | return res.status(REQUEST_ERROR_STATUS).send( 562 | {'error': 'Lost connection with node.'}); 563 | } 564 | } 565 | } else { 566 | return res.status(REQUEST_ERROR_STATUS).send( 567 | errorNeedToSetUpAPIMsg(websocket)) 568 | } 569 | } catch (e) { 570 | return res.status(REQUEST_ERROR_STATUS).send( 571 | {'error': e.toString()}); 572 | } 573 | }); 574 | 575 | app.get('/api/query/democracy/referendumCount', async function (req, res) { 576 | console.log('Received request for %s', req.url); 577 | try { 578 | // extract the web socket passed in the query 579 | const websocket = req.query.websocket; 580 | // check whether an api has been connected for that websocket 581 | if (websocket in apiProviderDict) { 582 | const apiResult = await substrateQuery.queryAPI( 583 | apiProviderDict[websocket].api, "democracy/referendumCount" 584 | ); 585 | if ('result' in apiResult) { 586 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 587 | } else { 588 | if (apiProviderDict[websocket].provider.isConnected) { 589 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 590 | } else { 591 | return res.status(REQUEST_ERROR_STATUS).send( 592 | {'error': 'Lost connection with node.'}); 593 | } 594 | } 595 | } else { 596 | return res.status(REQUEST_ERROR_STATUS).send( 597 | errorNeedToSetUpAPIMsg(websocket)) 598 | } 599 | } catch (e) { 600 | return res.status(REQUEST_ERROR_STATUS).send( 601 | {'error': e.toString()}); 602 | } 603 | }); 604 | 605 | app.get('/api/query/democracy/referendumInfoOf', async function (req, res) { 606 | console.log('Received request for %s', req.url); 607 | try { 608 | // extract the web socket passed in the query 609 | const websocket = req.query.websocket; 610 | // extract the referendumIndex passed in the query 611 | const referendumIndex = req.query.referendum_index; 612 | // check whether an api has been connected for that websocket 613 | if (websocket in apiProviderDict) { 614 | const apiResult = await substrateQuery.queryAPI( 615 | apiProviderDict[websocket].api, 616 | "democracy/referendumInfoOf", referendumIndex 617 | ); 618 | if ('result' in apiResult) { 619 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 620 | } else { 621 | if (apiProviderDict[websocket].provider.isConnected) { 622 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 623 | } else { 624 | return res.status(REQUEST_ERROR_STATUS).send( 625 | {'error': 'Lost connection with node.'}); 626 | } 627 | } 628 | } else { 629 | return res.status(REQUEST_ERROR_STATUS).send( 630 | errorNeedToSetUpAPIMsg(websocket)) 631 | } 632 | } catch (e) { 633 | return res.status(REQUEST_ERROR_STATUS).send( 634 | {'error': e.toString()}); 635 | } 636 | }); 637 | 638 | // ImOnline 639 | app.get('/api/query/imOnline/authoredBlocks', async function (req, res) { 640 | console.log('Received request for %s', req.url); 641 | try { 642 | // extract the web socket passed in the query 643 | const websocket = req.query.websocket; 644 | // extract the sessionIndex passed in the query 645 | const sessionIndex = req.query.session_index; 646 | // extract the validatorId passed in the query 647 | const validatorId = req.query.validator_id; 648 | // check whether an api has been connected for that websocket 649 | if (websocket in apiProviderDict) { 650 | const apiResult = await substrateQuery.queryAPI( 651 | apiProviderDict[websocket].api, "imOnline/authoredBlocks", 652 | sessionIndex, validatorId 653 | ); 654 | if ('result' in apiResult) { 655 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 656 | } else { 657 | if (apiProviderDict[websocket].provider.isConnected) { 658 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 659 | } else { 660 | return res.status(REQUEST_ERROR_STATUS).send( 661 | {'error': 'Lost connection with node.'}); 662 | } 663 | } 664 | } else { 665 | return res.status(REQUEST_ERROR_STATUS).send( 666 | errorNeedToSetUpAPIMsg(websocket)) 667 | } 668 | } catch (e) { 669 | return res.status(REQUEST_ERROR_STATUS).send( 670 | {'error': e.toString()}); 671 | } 672 | }); 673 | 674 | app.get('/api/query/imOnline/receivedHeartbeats', async function (req, res) { 675 | console.log('Received request for %s', req.url); 676 | try { 677 | // extract the web socket passed in the query 678 | const websocket = req.query.websocket; 679 | // extract the sessionIndex passed in the query 680 | const sessionIndex = req.query.session_index; 681 | // extract the validatorId passed in the query 682 | const authIndex = req.query.auth_index; 683 | // check whether an api has been connected for that websocket 684 | if (websocket in apiProviderDict) { 685 | const apiResult = await substrateQuery.queryAPI( 686 | apiProviderDict[websocket].api, 687 | "imOnline/receivedHeartbeats", sessionIndex, authIndex 688 | ); 689 | if ('result' in apiResult) { 690 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 691 | } else { 692 | if (apiProviderDict[websocket].provider.isConnected) { 693 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 694 | } else { 695 | return res.status(REQUEST_ERROR_STATUS).send( 696 | {'error': 'Lost connection with node.'}); 697 | } 698 | } 699 | } else { 700 | return res.status(REQUEST_ERROR_STATUS).send( 701 | errorNeedToSetUpAPIMsg(websocket)) 702 | } 703 | } catch (e) { 704 | return res.status(REQUEST_ERROR_STATUS).send( 705 | {'error': e.toString()}); 706 | } 707 | }); 708 | 709 | // Session 710 | app.get('/api/query/session/currentIndex', async function (req, res) { 711 | console.log('Received request for %s', req.url); 712 | try { 713 | // extract the web socket passed in the query 714 | const websocket = req.query.websocket; 715 | // check whether an api has been connected for that websocket 716 | if (websocket in apiProviderDict) { 717 | const apiResult = await substrateQuery.queryAPI( 718 | apiProviderDict[websocket].api, "session/currentIndex"); 719 | if ('result' in apiResult) { 720 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 721 | } else { 722 | if (apiProviderDict[websocket].provider.isConnected) { 723 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 724 | } else { 725 | return res.status(REQUEST_ERROR_STATUS).send( 726 | {'error': 'Lost connection with node.'}); 727 | } 728 | } 729 | } else { 730 | return res.status(REQUEST_ERROR_STATUS).send( 731 | errorNeedToSetUpAPIMsg(websocket)) 732 | } 733 | } catch (e) { 734 | return res.status(REQUEST_ERROR_STATUS).send( 735 | {'error': e.toString()}); 736 | } 737 | }); 738 | 739 | app.get('/api/query/session/disabledValidators', async function (req, res) { 740 | console.log('Received request for %s', req.url); 741 | try { 742 | // extract the web socket passed in the query 743 | const websocket = req.query.websocket; 744 | // check whether an api has been connected for that websocket 745 | if (websocket in apiProviderDict) { 746 | const apiResult = await substrateQuery.queryAPI( 747 | apiProviderDict[websocket].api, "session/disabledValidators" 748 | ); 749 | if ('result' in apiResult) { 750 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 751 | } else { 752 | if (apiProviderDict[websocket].provider.isConnected) { 753 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 754 | } else { 755 | return res.status(REQUEST_ERROR_STATUS).send( 756 | {'error': 'Lost connection with node.'}); 757 | } 758 | } 759 | } else { 760 | return res.status(REQUEST_ERROR_STATUS).send( 761 | errorNeedToSetUpAPIMsg(websocket)) 762 | } 763 | } catch (e) { 764 | return res.status(REQUEST_ERROR_STATUS).send( 765 | {'error': e.toString()}); 766 | } 767 | }); 768 | 769 | app.get('/api/query/session/validators', async function (req, res) { 770 | console.log('Received request for %s', req.url); 771 | try { 772 | // extract the web socket passed in the query 773 | const websocket = req.query.websocket; 774 | // check whether an api has been connected for that websocket 775 | if (websocket in apiProviderDict) { 776 | const apiResult = await substrateQuery.queryAPI( 777 | apiProviderDict[websocket].api, "session/validators"); 778 | if ('result' in apiResult) { 779 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 780 | } else { 781 | if (apiProviderDict[websocket].provider.isConnected) { 782 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 783 | } else { 784 | return res.status(REQUEST_ERROR_STATUS).send( 785 | {'error': 'Lost connection with node.'}); 786 | } 787 | } 788 | } else { 789 | return res.status(REQUEST_ERROR_STATUS).send( 790 | errorNeedToSetUpAPIMsg(websocket)) 791 | } 792 | } catch (e) { 793 | return res.status(REQUEST_ERROR_STATUS).send( 794 | {'error': e.toString()}); 795 | } 796 | }); 797 | 798 | // Staking 799 | app.get('/api/query/staking/activeEra', async function (req, res) { 800 | console.log('Received request for %s', req.url); 801 | try { 802 | // extract the web socket passed in the query 803 | const websocket = req.query.websocket; 804 | // check whether an api has been connected for that websocket 805 | if (websocket in apiProviderDict) { 806 | const apiResult = await substrateQuery.queryAPI( 807 | apiProviderDict[websocket].api, "staking/activeEra"); 808 | if ('result' in apiResult) { 809 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 810 | } else { 811 | if (apiProviderDict[websocket].provider.isConnected) { 812 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 813 | } else { 814 | return res.status(REQUEST_ERROR_STATUS).send( 815 | {'error': 'Lost connection with node.'}); 816 | } 817 | } 818 | } else { 819 | return res.status(REQUEST_ERROR_STATUS).send( 820 | errorNeedToSetUpAPIMsg(websocket)) 821 | } 822 | } catch (e) { 823 | return res.status(REQUEST_ERROR_STATUS).send( 824 | {'error': e.toString()}); 825 | } 826 | }); 827 | 828 | app.get('/api/query/staking/bonded', async function (req, res) { 829 | console.log('Received request for %s', req.url); 830 | try { 831 | // extract the web socket passed in the query 832 | const websocket = req.query.websocket; 833 | // extract the accountId passed in the query 834 | const accountId = req.query.account_id; 835 | // check whether an api has been connected for that websocket 836 | if (websocket in apiProviderDict) { 837 | const apiResult = await substrateQuery.queryAPI( 838 | apiProviderDict[websocket].api, "staking/bonded", 839 | accountId 840 | ); 841 | if ('result' in apiResult) { 842 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 843 | } else { 844 | if (apiProviderDict[websocket].provider.isConnected) { 845 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 846 | } else { 847 | return res.status(REQUEST_ERROR_STATUS).send( 848 | {'error': 'Lost connection with node.'}); 849 | } 850 | } 851 | } else { 852 | return res.status(REQUEST_ERROR_STATUS).send( 853 | errorNeedToSetUpAPIMsg(websocket)) 854 | } 855 | } catch (e) { 856 | return res.status(REQUEST_ERROR_STATUS).send( 857 | {'error': e.toString()}); 858 | } 859 | }); 860 | 861 | app.get('/api/query/staking/erasRewardPoints', async function (req, res) { 862 | console.log('Received request for %s', req.url); 863 | try { 864 | // extract the web socket passed in the query 865 | const websocket = req.query.websocket; 866 | // extract the eraIndex passed in the query (optional) 867 | const eraIndex = req.query.era_index; 868 | // check whether an api has been connected for that websocket 869 | if (websocket in apiProviderDict) { 870 | const apiResult = await substrateQuery.queryAPI( 871 | apiProviderDict[websocket].api, "staking/erasRewardPoints", 872 | eraIndex 873 | ); 874 | if ('result' in apiResult) { 875 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 876 | } else { 877 | if (apiProviderDict[websocket].provider.isConnected) { 878 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 879 | } else { 880 | return res.status(REQUEST_ERROR_STATUS).send( 881 | {'error': 'Lost connection with node.'}); 882 | } 883 | } 884 | } else { 885 | return res.status(REQUEST_ERROR_STATUS).send( 886 | errorNeedToSetUpAPIMsg(websocket)) 887 | } 888 | } catch (e) { 889 | return res.status(REQUEST_ERROR_STATUS).send( 890 | {'error': e.toString()}); 891 | } 892 | }); 893 | 894 | app.get('/api/query/staking/erasStakers', async function (req, res) { 895 | console.log('Received request for %s', req.url); 896 | try { 897 | // extract the web socket passed in the query 898 | const websocket = req.query.websocket; 899 | // extract the accountId passed in the query 900 | const accountId = req.query.account_id; 901 | // extract the eraIndex passed in the query (optional) 902 | const eraIndex = req.query.era_index; 903 | // check whether an api has been connected for that websocket 904 | if (websocket in apiProviderDict) { 905 | const apiResult = await substrateQuery.queryAPI( 906 | apiProviderDict[websocket].api, "staking/erasStakers", 907 | accountId, eraIndex 908 | ); 909 | if ('result' in apiResult) { 910 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 911 | } else { 912 | if (apiProviderDict[websocket].provider.isConnected) { 913 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 914 | } else { 915 | return res.status(REQUEST_ERROR_STATUS).send( 916 | {'error': 'Lost connection with node.'}); 917 | } 918 | } 919 | } else { 920 | return res.status(REQUEST_ERROR_STATUS).send( 921 | errorNeedToSetUpAPIMsg(websocket)) 922 | } 923 | } catch (e) { 924 | return res.status(REQUEST_ERROR_STATUS).send( 925 | {'error': e.toString()}); 926 | } 927 | }); 928 | 929 | app.get('/api/query/staking/erasTotalStake', async function (req, res) { 930 | console.log('Received request for %s', req.url); 931 | try { 932 | // extract the web socket passed in the query 933 | const websocket = req.query.websocket; 934 | // extract the eraIndex passed in the query (optional) 935 | const eraIndex = req.query.era_index; 936 | // check whether an api has been connected for that websocket 937 | if (websocket in apiProviderDict) { 938 | const apiResult = await substrateQuery.queryAPI( 939 | apiProviderDict[websocket].api, "staking/erasTotalStake", 940 | eraIndex 941 | ); 942 | if ('result' in apiResult) { 943 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 944 | } else { 945 | if (apiProviderDict[websocket].provider.isConnected) { 946 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 947 | } else { 948 | return res.status(REQUEST_ERROR_STATUS).send( 949 | {'error': 'Lost connection with node.'}); 950 | } 951 | } 952 | } else { 953 | return res.status(REQUEST_ERROR_STATUS).send( 954 | errorNeedToSetUpAPIMsg(websocket)) 955 | } 956 | } catch (e) { 957 | return res.status(REQUEST_ERROR_STATUS).send( 958 | {'error': e.toString()}); 959 | } 960 | }); 961 | 962 | app.get('/api/query/staking/erasValidatorReward', async function (req, res) { 963 | console.log('Received request for %s', req.url); 964 | try { 965 | // extract the web socket passed in the query 966 | const websocket = req.query.websocket; 967 | // extract the eraIndex passed in the query (optional) 968 | const eraIndex = req.query.era_index; 969 | // check whether an api has been connected for that websocket 970 | if (websocket in apiProviderDict) { 971 | const apiResult = await substrateQuery.queryAPI( 972 | apiProviderDict[websocket].api, 973 | "staking/erasValidatorReward", eraIndex 974 | ); 975 | if ('result' in apiResult) { 976 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 977 | } else { 978 | if (apiProviderDict[websocket].provider.isConnected) { 979 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 980 | } else { 981 | return res.status(REQUEST_ERROR_STATUS).send( 982 | {'error': 'Lost connection with node.'}); 983 | } 984 | } 985 | } else { 986 | return res.status(REQUEST_ERROR_STATUS).send( 987 | errorNeedToSetUpAPIMsg(websocket)) 988 | } 989 | } catch (e) { 990 | return res.status(REQUEST_ERROR_STATUS).send( 991 | {'error': e.toString()}); 992 | } 993 | }); 994 | 995 | app.get('/api/query/staking/payee', async function (req, res) { 996 | console.log('Received request for %s', req.url); 997 | try { 998 | // extract the web socket passed in the query 999 | const websocket = req.query.websocket; 1000 | // extract the accountId passed in the query 1001 | const accountId = req.query.account_id; 1002 | // check whether an api has been connected for that websocket 1003 | if (websocket in apiProviderDict) { 1004 | const apiResult = await substrateQuery.queryAPI( 1005 | apiProviderDict[websocket].api, "staking/payee", 1006 | accountId 1007 | ); 1008 | if ('result' in apiResult) { 1009 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 1010 | } else { 1011 | if (apiProviderDict[websocket].provider.isConnected) { 1012 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 1013 | } else { 1014 | return res.status(REQUEST_ERROR_STATUS).send( 1015 | {'error': 'Lost connection with node.'}); 1016 | } 1017 | } 1018 | } else { 1019 | return res.status(REQUEST_ERROR_STATUS).send( 1020 | errorNeedToSetUpAPIMsg(websocket)) 1021 | } 1022 | } catch (e) { 1023 | return res.status(REQUEST_ERROR_STATUS).send( 1024 | {'error': e.toString()}); 1025 | } 1026 | }); 1027 | 1028 | app.get('/api/query/staking/unappliedSlashes', async function (req, res) { 1029 | console.log('Received request for %s', req.url); 1030 | try { 1031 | // extract the web socket passed in the query 1032 | const websocket = req.query.websocket; 1033 | // extract the eraIndex passed in the query (optional) 1034 | const eraIndex = req.query.era_index; 1035 | // check whether an api has been connected for that websocket 1036 | if (websocket in apiProviderDict) { 1037 | const apiResult = await substrateQuery.queryAPI( 1038 | apiProviderDict[websocket].api, 1039 | "staking/unappliedSlashes", eraIndex 1040 | ); 1041 | if ('result' in apiResult) { 1042 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 1043 | } else { 1044 | if (apiProviderDict[websocket].provider.isConnected) { 1045 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 1046 | } else { 1047 | return res.status(REQUEST_ERROR_STATUS).send( 1048 | {'error': 'Lost connection with node.'}); 1049 | } 1050 | } 1051 | } else { 1052 | return res.status(REQUEST_ERROR_STATUS).send( 1053 | errorNeedToSetUpAPIMsg(websocket)) 1054 | } 1055 | } catch (e) { 1056 | return res.status(REQUEST_ERROR_STATUS).send( 1057 | {'error': e.toString()}); 1058 | } 1059 | }); 1060 | 1061 | app.get('/api/query/staking/validators', async function (req, res) { 1062 | console.log('Received request for %s', req.url); 1063 | try { 1064 | // extract the web socket passed in the query 1065 | const websocket = req.query.websocket; 1066 | // extract the accountId passed in the query 1067 | const accountId = req.query.account_id; 1068 | // check whether an api has been connected for that websocket 1069 | if (websocket in apiProviderDict) { 1070 | const apiResult = await substrateQuery.queryAPI( 1071 | apiProviderDict[websocket].api, "staking/validators", 1072 | accountId 1073 | ); 1074 | if ('result' in apiResult) { 1075 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 1076 | } else { 1077 | if (apiProviderDict[websocket].provider.isConnected) { 1078 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 1079 | } else { 1080 | return res.status(REQUEST_ERROR_STATUS).send( 1081 | {'error': 'Lost connection with node.'}); 1082 | } 1083 | } 1084 | } else { 1085 | return res.status(REQUEST_ERROR_STATUS).send( 1086 | errorNeedToSetUpAPIMsg(websocket)) 1087 | } 1088 | } catch (e) { 1089 | return res.status(REQUEST_ERROR_STATUS).send( 1090 | {'error': e.toString()}); 1091 | } 1092 | }); 1093 | 1094 | // System 1095 | app.get('/api/query/system/events', async function (req, res) { 1096 | console.log('Received request for %s', req.url); 1097 | try { 1098 | // extract the web socket passed in the query 1099 | const websocket = req.query.websocket; 1100 | // extract the blockHash passed in the query (optional) 1101 | const blockHash = req.query.block_hash; 1102 | // check whether an api has been connected for that websocket 1103 | if (websocket in apiProviderDict) { 1104 | const apiResult = await substrateQuery.queryAPI( 1105 | apiProviderDict[websocket].api, "system/events", blockHash); 1106 | if ('result' in apiResult) { 1107 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 1108 | } else { 1109 | if (apiProviderDict[websocket].provider.isConnected) { 1110 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 1111 | } else { 1112 | return res.status(REQUEST_ERROR_STATUS).send( 1113 | {'error': 'Lost connection with node.'}); 1114 | } 1115 | } 1116 | } else { 1117 | return res.status(REQUEST_ERROR_STATUS).send( 1118 | errorNeedToSetUpAPIMsg(websocket)) 1119 | } 1120 | } catch (e) { 1121 | return res.status(REQUEST_ERROR_STATUS).send( 1122 | {'error': e.toString()}); 1123 | } 1124 | }); 1125 | 1126 | // Custom Endpoints 1127 | app.get('/api/custom/getSlashAmount', async function (req, res) { 1128 | console.log('Received request for %s', req.url); 1129 | try { 1130 | // extract the web socket passed in the query 1131 | const websocket = req.query.websocket; 1132 | // extract the blockHash passed in the query (optional) 1133 | const blockHash = req.query.block_hash; 1134 | // extract the accountAddress passed in the query 1135 | const accountAddress = req.query.account_address; 1136 | // check whether an api has been connected for that websocket 1137 | if (websocket in apiProviderDict) { 1138 | const apiResult = await substrateQuery.queryAPI( 1139 | apiProviderDict[websocket].api, "custom/getSlashAmount", 1140 | blockHash, accountAddress 1141 | ); 1142 | if ('result' in apiResult) { 1143 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 1144 | } else { 1145 | if (apiProviderDict[websocket].provider.isConnected) { 1146 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 1147 | } else { 1148 | return res.status(REQUEST_ERROR_STATUS).send( 1149 | {'error': 'Lost connection with node.'}); 1150 | } 1151 | } 1152 | } else { 1153 | return res.status(REQUEST_ERROR_STATUS).send( 1154 | errorNeedToSetUpAPIMsg(websocket)) 1155 | } 1156 | } catch (e) { 1157 | return res.status(REQUEST_ERROR_STATUS).send( 1158 | {'error': e.toString()}); 1159 | } 1160 | }); 1161 | 1162 | // API Derive Endpoints 1163 | // Staking 1164 | app.get('/api/derive/staking/validators', async function (req, res) { 1165 | console.log('Received request for %s', req.url); 1166 | try { 1167 | // extract the web socket passed in the query 1168 | const websocket = req.query.websocket; 1169 | // check whether an api has been connected for that websocket 1170 | if (websocket in apiProviderDict) { 1171 | const apiResult = await substrateDerive.deriveAPI( 1172 | apiProviderDict[websocket].api, "staking/validators"); 1173 | if ('result' in apiResult) { 1174 | return res.status(REQUEST_SUCCESS_STATUS).send(apiResult); 1175 | } else { 1176 | if (apiProviderDict[websocket].provider.isConnected) { 1177 | return res.status(REQUEST_ERROR_STATUS).send(apiResult); 1178 | } else { 1179 | return res.status(REQUEST_ERROR_STATUS).send( 1180 | {'error': 'Lost connection with node.'}); 1181 | } 1182 | } 1183 | } else { 1184 | return res.status(REQUEST_ERROR_STATUS).send( 1185 | errorNeedToSetUpAPIMsg(websocket)) 1186 | } 1187 | } catch (e) { 1188 | return res.status(REQUEST_ERROR_STATUS).send( 1189 | {'error': e.toString()}); 1190 | } 1191 | }); 1192 | } 1193 | 1194 | startPolkadotAPI(); 1195 | -------------------------------------------------------------------------------- /src/utils/timeout.js: -------------------------------------------------------------------------------- 1 | const Timeout = require('await-timeout'); 2 | 3 | module.exports = { 4 | callFnWithTimeoutSafely: async function ( 5 | callback, params, timeout, returnIfTimeoutExceeded 6 | ) { 7 | const timer = new Timeout(); 8 | try { 9 | return await Promise.race([ 10 | callback.apply(this, params), 11 | Timeout.set(timeout, returnIfTimeoutExceeded) 12 | ]); 13 | } finally { 14 | timer.clear(); 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /test/report_system_testing.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SimplyStaking/polkadot_api_server/8d4014f4cdd0a0dd31c733ae1647b66f9cb269d6/test/report_system_testing.xlsx --------------------------------------------------------------------------------