├── .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 |
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 |
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
--------------------------------------------------------------------------------