init
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
5
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
module.exports = require('./babel.config.js');
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
exclude_patterns:
|
||||||
|
- "**/*.spec.js"
|
||||||
|
- "**/*.spec.ts"
|
||||||
|
- "**/*.test.js"
|
||||||
|
- "**/*.test.ts"
|
||||||
|
- "docs/**/*.js"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
root = true
|
||||||
|
[*]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
||||||
|
tab_width=2
|
||||||
|
end_of_line=lf
|
||||||
|
charset=utf-8
|
||||||
|
trim_trailing_whitespace=true
|
||||||
|
max_line_length=120
|
||||||
|
insert_final_newline=true
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
_book/
|
||||||
|
build/
|
||||||
|
build-docs/
|
||||||
|
coverage/
|
||||||
|
docs/.vuepress/dist/
|
||||||
|
node_modules/
|
||||||
|
tmp/
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.npmrc
|
||||||
|
lerna-debug.log*
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
package-lock.json
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "10"
|
||||||
|
env:
|
||||||
|
- GH_PAGES_SRC="build-docs"
|
||||||
|
cache:
|
||||||
|
yarn: true
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
before_install:
|
||||||
|
- curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||||
|
- export PATH=$HOME/.yarn/bin::$(yarn global bin):$PATH
|
||||||
|
script:
|
||||||
|
- yarn plugnet-dev-travis-build
|
||||||
|
deploy:
|
||||||
|
skip_cleanup: true
|
||||||
|
provider: script
|
||||||
|
script: yarn plugnet-dev-travis-deploy
|
||||||
|
on:
|
||||||
|
branch: master
|
||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
# 0.76.1
|
||||||
|
|
||||||
|
- Caching improvements (duplicate queries, no duplicate subscriptions)
|
||||||
|
- Experimental contract API
|
||||||
|
- Update @polkadot/keyring to enable Alice's stash account on dev chains
|
||||||
|
- Update @polkadot/util-crypto with smaller footprint
|
||||||
|
|
||||||
|
# 0.75.1
|
||||||
|
|
||||||
|
- Start journey to 1.0
|
||||||
|
|
||||||
|
# 0.53.1
|
||||||
|
|
||||||
|
- Change spelling to US English as per substrate master (1.0-rc1). Breaking changes as a result:
|
||||||
|
- For extrinsic status results, if you have checked the type returns, i.e. `result.type === 'Finalised'` now check on the status for `result.status.isFinalized` or `result.status.isBroadcast`, ... (the `type` property is now accessible only on `result.status.type`)
|
||||||
|
- If using `subscribeFinalisedHeads` update this to `subscribeFinalizedHeads` (likewise `getFinalisedHead` should be updated to `getFinalizedHead` and `derive.bestNumberFinalized`)
|
||||||
|
- The underlying ss58 addess checksums have changed in the keyring along with the latest specs
|
||||||
|
- All examples have been updated with sr25519 addresses (with the new checksums)
|
||||||
|
|
||||||
|
# 0.52.1
|
||||||
|
|
||||||
|
- Support queries to linked mapped storage (found in new staking interfaces)
|
||||||
|
- Add `derive.staking.controllers` to retrieve all active staking controllers
|
||||||
|
- Align types as per latest substrate master
|
||||||
|
- PeerInfo from system_peers does not have the index field anymore (dropped in substrate)
|
||||||
|
- Allow parsing of V3 metadata with DoubleMap support
|
||||||
|
- Check for single instances for api and types as they are loaded (assertSingletonPackage)
|
||||||
|
|
||||||
|
# 0.51.1
|
||||||
|
|
||||||
|
- Support metadata V2 as per latest substrate master
|
||||||
|
- Update metadata with new types as per lastest substrate master
|
||||||
|
|
||||||
|
# 0.50.1
|
||||||
|
|
||||||
|
- Lastest util-crypto (usage of WASM with JS fallbacks if not available)
|
||||||
|
- Update upstream @polkadot dependencies (for new crypto)
|
||||||
|
|
||||||
|
# 0.49.1
|
||||||
|
|
||||||
|
- Fix large message signing on non-known nodes (default is now hashing, there has been enough time between upgrades)
|
||||||
|
|
||||||
|
# 0.48.1
|
||||||
|
|
||||||
|
- Pull in new sr25519 capable keyring for dev nodes
|
||||||
|
- When using dev mode, it assumes that the node is the latest with derived sr25519 keys
|
||||||
|
|
||||||
|
# 0.47.1
|
||||||
|
|
||||||
|
- Swap to publishing -beta.x on merge (non-breaking testing)
|
||||||
|
|
||||||
|
# 0.46.1
|
||||||
|
|
||||||
|
- Extended type registration to now handle internal types as well. Additionally the built-in Extrinsic type can now we overridden with a custom version.
|
||||||
|
- Where `Extrinsic` and `Method` is used as types, considder importing `{ IMethod, IExtrinsic }` from `@polkadot/types/types`, especially in the cases where this is uased from a `SubmittableExtrinsic`
|
||||||
|
- The `typeRegistry` constant is now `getTypeRegistry()` as a function
|
||||||
|
|
||||||
|
# 0.45.1
|
||||||
|
|
||||||
|
- Storage with option values now correctly return `Option<Type>` and is indicated as such in the documentation
|
||||||
|
|
||||||
|
```js
|
||||||
|
// old
|
||||||
|
const ll = await api.query.session.lastLengthChange();
|
||||||
|
|
||||||
|
console.log('ll', ll /* BlockNumber */); // would be 0 if not set yet
|
||||||
|
|
||||||
|
// new
|
||||||
|
const llo = await api.query.session.lastLengthChange();
|
||||||
|
|
||||||
|
console.log('llo', llo.unwrapOr('not set') /* Option<BlockNumber> */)
|
||||||
|
```
|
||||||
|
|
||||||
|
# 0.44.1
|
||||||
|
|
||||||
|
- Split primitives and types into seperate folders. This should not affect external use since the exports remain the same, however does have an impact where classes are referenced directly. e.g.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// old (affected)
|
||||||
|
import Method from '@polkadot/types/Method';
|
||||||
|
import Signature from '@polkadot/types/Signature';
|
||||||
|
|
||||||
|
// new locations
|
||||||
|
import Method from '@polkadot/types/primitive/Method';
|
||||||
|
import Signature from '@polkadot/types/type/Signature';
|
||||||
|
|
||||||
|
// unaffected
|
||||||
|
import { Method, Signature } from '@polkadot/types';
|
||||||
|
```
|
||||||
|
|
||||||
|
# 0.43.1
|
||||||
|
|
||||||
|
- Intrduces support for the new keyring with sr25519 support in addition to ed25519. While this does not change the exposed API, it is considerred breaking since @polkadot/keuyring has interface changes. (Unless needed, don't rush the upgrade)
|
||||||
|
|
||||||
|
# 0.42.1
|
||||||
|
|
||||||
|
- Support for substrate hash signing with implVersion >= 18
|
||||||
|
- Changed signatures for Extrinsic signing, this should not have (much) of an impact since it is generally not used directly. SubmittableExtrinsic (as exposed by the API), supports the old-style use.
|
||||||
|
|
||||||
|
# 0.41.1
|
||||||
|
|
||||||
|
- Support the V1 metadata specification from Substrate in addition to the currently testnet-active V0 version
|
||||||
|
|
||||||
|
# 0.40.1
|
||||||
|
|
||||||
|
- The API interfaces now require a WS-compatible provider, e.g. subscription support is a must. Previously the HTTPProvider could be use (although it was very limited in the interactions).
|
||||||
|
|
||||||
|
# 0.39.1
|
||||||
|
|
||||||
|
- The Promise API now returns a `Promise<UnsubFunction>` instead of `UnsubFunction` when making subscriptions.
|
||||||
|
|
||||||
|
# 0.38.1
|
||||||
|
|
||||||
|
Substrate has been updated with a breaking new transaction format where the Index/Nonce is now encoded as a Compact. This change is being rolled out to both Alexander (Polkadot testnet) as well as Charred Cherry (Substrate testnet) - transactions between old and new are not compatible.
|
||||||
|
|
||||||
|
# 0.37.1
|
||||||
|
|
||||||
|
api-observable has been removed. This was only used in /apps and inconsistent with the api/rx and api/promise APIs. Future work will include derivates like was included in api-observable into the base.
|
||||||
|
|
||||||
|
Tuples now return single types when only one type is available, i.e. `(AccountId)` would now resolve as `AccountId`. The extra type wrapper adds no benefit to users here.
|
||||||
|
|
||||||
|
# 0.36.1
|
||||||
|
|
||||||
|
Api Promise has been updated in the way we deal with subscriptions. Previously a subscription returned `Promise<number>` where the caller was to keep track of the id and use it in subsequent unsubscribes. Now any subscriptions return an unsubscribe/destroy function `(): void`, that is use for removing the subscriptions, e.g.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const unsubscribe = api.query.balance.freeBalance(Alice, (balance) => {...});
|
||||||
|
|
||||||
|
unsubscribe(); // here we destroy the subscription
|
||||||
|
```
|
||||||
|
|
||||||
|
# 0.35.1
|
||||||
|
|
||||||
|
Swapped to new metadata structures from Substrate. If the API is not working with your node, update Substrate to latest master branch. (Or 0.9.1 for Charred Cherry). Dropped support for old metadata as found as far back as BBQ Birch.
|
||||||
|
|
||||||
|
# 0.34.1
|
||||||
|
|
||||||
|
Changed the send signature (for future expansion of eg. events) to return `result: { status: ExtrinsicStatus }` instead of `status: ExtrinsicStatus`. For most cases where only status `type` checks are used, i.e. `status.type === 'Finalised'` this should not be a breaking change. Deep inspection of the status object however will need to adapt.
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## What?
|
||||||
|
|
||||||
|
Individuals making significant and valuable contributions are given commit-access to a project to contribute as they see fit.
|
||||||
|
A project is more like an open wiki than a standard guarded open source project.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
There are a few basic ground-rules for contributors (including the maintainer(s) of the project):
|
||||||
|
|
||||||
|
1. **No `--force` pushes** or modifying the Git history in any way. If you need to rebase, ensure you do it in your own repo.
|
||||||
|
2. **Non-master branches**, prefixed with a short name moniker (e.g. `<initials>-<feature>`) must be used for ongoing work.
|
||||||
|
3. **All modifications** must be made in a **pull-request** to solicit feedback from other contributors.
|
||||||
|
4. A pull-request *must not be merged until CI* has finished successfully.
|
||||||
|
|
||||||
|
#### Merging pull requests once CI is successful:
|
||||||
|
- A pull request with no large change to logic that is an urgent fix may be merged after a non-author contributor has reviewed it well.
|
||||||
|
- No PR should be merged until all reviews' comments are addressed.
|
||||||
|
|
||||||
|
#### Reviewing pull requests:
|
||||||
|
When reviewing a pull request, the end-goal is to suggest useful changes to the author. Reviews should finish with approval unless there are issues that would result in:
|
||||||
|
|
||||||
|
- Buggy behaviour.
|
||||||
|
- Undue maintenance burden.
|
||||||
|
- Breaking with house coding style.
|
||||||
|
- Pessimisation (i.e. reduction of speed as measured in the projects benchmarks).
|
||||||
|
- Feature reduction (i.e. it removes some aspect of functionality that a significant minority of users rely on).
|
||||||
|
- Uselessness (i.e. it does not strictly add a feature or fix a known issue).
|
||||||
|
|
||||||
|
#### Reviews may not be used as an effective veto for a PR because:
|
||||||
|
- There exists a somewhat cleaner/better/faster way of accomplishing the same feature/fix.
|
||||||
|
- It does not fit well with some other contributors' longer-term vision for the project.
|
||||||
|
|
||||||
|
## Releases
|
||||||
|
|
||||||
|
Declaring formal releases remains the prerogative of the project maintainer(s).
|
||||||
|
|
||||||
|
## Changes to this arrangement
|
||||||
|
|
||||||
|
This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change.
|
||||||
|
|
||||||
|
## Heritage
|
||||||
|
|
||||||
|
These contributing guidelines are modified from the "OPEN Open Source Project" guidelines for the Level project: [https://github.com/Level/community/blob/master/CONTRIBUTING.md](https://github.com/Level/community/blob/master/CONTRIBUTING.md)
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# @plugnet/api
|
||||||
|
|
||||||
|
This library provides a clean wrapper around all the methods exposed by a Polkadot/Subtrate network client and defines all the types exposed by a node. For complete documentation around the classes, interfaces and their use, visit the [documentation portal](https://polkadot.js.org/api/).
|
||||||
|
|
||||||
|
## tutorials
|
||||||
|
|
||||||
|
Looking for tutorials to get started? Look at [examples](https://polkadot.js.org/api/examples/promise/) for guides on how to use the API to make queries and submit transactions.
|
||||||
|
|
||||||
|
## overview
|
||||||
|
|
||||||
|
The API is split up into a number of internal packages -
|
||||||
|
|
||||||
|
- [@polkadot/api](packages/api/) The API library, providing both Promise and RxJS Observable-based interfaces. This is the main user-facing entry point.
|
||||||
|
- [@polkadot/api-derive](packages/api-derive/) Derived results that are injected into the API, allowing for combinations of various query results (only used internally and exposed on the Api instances via `api.derive.*`)
|
||||||
|
- [@polkadot/rpc-core](packages/rpc-core/) Wrapper around all [JSON-RPC methods](https://polkadot.js.org/api/METHODS_RPC.html) exposed by a Polkadot network client
|
||||||
|
- [@polkadot/rpc-provider](packages/rpc-provider/) Providers for connecting to nodes, including WebSockets and Http
|
||||||
|
- [@polkadot/rpc-rx](packages/rpc-rx/) A RxJs Observable wrapper around [@polkadot/rpc-provider](packages/rpc-provider)
|
||||||
|
|
||||||
|
Type definitions for interfaces as exposed by Polkadot & Substrate clients -
|
||||||
|
|
||||||
|
- [@polkadot/extrinsics](packages/type-extrinsics/) Base extrinsic definitions & codecs
|
||||||
|
- [@polkadot/jsonrpc](packages/type-jsonrpc/) Definitions for JSONRPC endpoints
|
||||||
|
- [@polkadot/storage](packages/type-storage/) Definitions for storage entries
|
||||||
|
- [@polkadot/types](packages/types/) Codecs for all Polkadot primitives
|
||||||
|
|
||||||
|
## development
|
||||||
|
|
||||||
|
Contributions are welcome!
|
||||||
|
|
||||||
|
To start off, this repo (along with others in the [@polkadot](https://github.com/polkadot-js/) family) uses yarn workspaces to organise the code. As such, after cloning, its dependencies _should_ be installed via `yarn`, not via npm; the latter will result in broken dependencies.
|
||||||
|
|
||||||
|
To get started -
|
||||||
|
|
||||||
|
1. Clone the repo locally, via `git clone https://github.com/polkadot-js/api <optional local path>`
|
||||||
|
2. Ensure that you have a recent version of Node.js, for development purposes [Node 10](https://nodejs.org/en/) is recommended.
|
||||||
|
3. Ensure that you have a recent version of Yarn, for development purposes [Yarn >=1.10.1](https://yarnpkg.com/docs/install) is required.
|
||||||
|
4. Install the dependencies by running `yarn`
|
||||||
|
5. Build the everything via `yarn run build`
|
||||||
|
6. You can also launch the API Docs, via `yarn vuepress dev docs`
|
||||||
|
7. Access the docs via [http://localhost:8080](http://localhost:8080)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: '@plugnet/dev/config/babel'
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const config = require('@plugnet/dev/config/jest');
|
||||||
|
|
||||||
|
module.exports = Object.assign({}, config, {
|
||||||
|
moduleNameMapper: {
|
||||||
|
'@plugnet/api-derive(.*)$': '<rootDir>/packages/api-derive/src/$1',
|
||||||
|
'@plugnet/api(.*)$': '<rootDir>/packages/api/src/$1',
|
||||||
|
'@plugnet/rpc-(core|provider|rx)(.*)$': '<rootDir>/packages/rpc-$1/src/$2',
|
||||||
|
'@plugnet/extrinsics(.*)$': '<rootDir>/packages/type-extrinsics/src/$1',
|
||||||
|
'@plugnet/jsonrpc(.*)$': '<rootDir>/packages/type-jsonrpc/src/$1',
|
||||||
|
'@plugnet/storage(.*)$': '<rootDir>/packages/type-storage/src/$1',
|
||||||
|
'@plugnet/types(.*)$': '<rootDir>/packages/types/src/$1',
|
||||||
|
'@plugnet/keyring(.*)$': '<rootDir>/packages/keyring/src/$1',
|
||||||
|
'@plugnet/util(.*)$': '<rootDir>/packages/util/src/$1',
|
||||||
|
'@plugnet/util-crypto(.*)$': '<rootDir>/packages/util-crypto/src/$1'
|
||||||
|
},
|
||||||
|
modulePathIgnorePatterns: [
|
||||||
|
'<rootDir>/packages/api/build',
|
||||||
|
'<rootDir>/packages/api-derive/build',
|
||||||
|
'<rootDir>/packages/rpc-core/build',
|
||||||
|
'<rootDir>/packages/rpc-provider/build',
|
||||||
|
'<rootDir>/packages/rpc-rx/build',
|
||||||
|
'<rootDir>/packages/type-extrinsics/build',
|
||||||
|
'<rootDir>/packages/type-jsonrpc/build',
|
||||||
|
'<rootDir>/packages/type-storage/build/',
|
||||||
|
'<rootDir>/packages/types/build',
|
||||||
|
'<rootDir>/packages/keyring/build',
|
||||||
|
'<rootDir>/packages/util/build/',
|
||||||
|
'<rootDir>/packages/util-crypto/build'
|
||||||
|
]
|
||||||
|
});
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"npmClient": "yarn",
|
||||||
|
"useWorkspaces": true,
|
||||||
|
"command": {
|
||||||
|
"publish": {
|
||||||
|
"allowBranch": "master"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packages": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"version": "0.76.1"
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"version": "0.76.1",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0",
|
||||||
|
"yarn": "^1.10.1"
|
||||||
|
},
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"resolutions": {
|
||||||
|
"@vuepress/core": "1.0.0-alpha.44",
|
||||||
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
|
"typescript": "^3.4.1",
|
||||||
|
"vuepress": "1.0.0-alpha.44"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "plugnet-dev-build-ts && yarn run build:methodsdoc && plugnet-dev-build-docs",
|
||||||
|
"build:htmldoc": "yarn clean && typedoc --theme default --out docs/html",
|
||||||
|
"build:methodsdoc": "node packages/types/src/scripts/MetadataMdWrapper.js",
|
||||||
|
"check": "yarn lint",
|
||||||
|
"lint": "tslint --project . && tsc --noEmit --pretty",
|
||||||
|
"clean": "plugnet-dev-clean-build",
|
||||||
|
"postinstall": "plugnet-dev-yarn-only",
|
||||||
|
"test": "jest --coverage"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.4.3",
|
||||||
|
"@babel/register": "^7.4.0",
|
||||||
|
"@babel/runtime": "^7.4.3",
|
||||||
|
"@plugnet/dev": "file:///Users/moge/Workspace/centrality/plug/plugnet-js-dev/packages/dev/build",
|
||||||
|
"@polkadot/ts": "^0.1.56",
|
||||||
|
"gh-pages": "^2.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# @polkadot/api-derive
|
||||||
|
|
||||||
|
Common functions used across Polkadot, derived from RPC calls and storage queries.
|
||||||
|
|
||||||
|
## Classes
|
||||||
|
|
||||||
|
[Classes](SUMMARY.md)
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "@plugnet/api-derive",
|
||||||
|
"version": "0.76.1",
|
||||||
|
"description": "Common functions used across Polkadot, derived from RPC calls and storage queries.",
|
||||||
|
"main": "index.js",
|
||||||
|
"keywords": [
|
||||||
|
"Polkadot",
|
||||||
|
"Promise",
|
||||||
|
"RxJs"
|
||||||
|
],
|
||||||
|
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||||
|
"maintainers": [
|
||||||
|
"Jaco Greeff <jacogr@gmail.com>"
|
||||||
|
],
|
||||||
|
"contributors": [],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/polkadot-js/api.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/polkadot-js/api/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/api-derive#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.4.3",
|
||||||
|
"@plugnet/api": "^0.76.1",
|
||||||
|
"@plugnet/types": "^0.76.1",
|
||||||
|
"@types/memoizee": "^0.4.2",
|
||||||
|
"memoizee": "^0.4.14"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { map, startWith } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { decodeAddress } from '@plugnet/keyring';
|
||||||
|
import { AccountId, AccountIndex, Address } from '@plugnet/types';
|
||||||
|
import { isU8a } from '@plugnet/util';
|
||||||
|
|
||||||
|
import { idToIndex } from './idToIndex';
|
||||||
|
import { indexToId } from './indexToId';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
export type AccountIdAndIndex = [AccountId?, AccountIndex?];
|
||||||
|
|
||||||
|
export function idAndIndex (api: ApiInterface$Rx) {
|
||||||
|
return (address?: Address | AccountId | AccountIndex | string | null): Observable<AccountIdAndIndex> => {
|
||||||
|
try {
|
||||||
|
// yes, this can fail, don't care too much, catch will catch it
|
||||||
|
const decoded = isU8a(address)
|
||||||
|
? address
|
||||||
|
: decodeAddress((address || '').toString());
|
||||||
|
|
||||||
|
if (decoded.length === 32) {
|
||||||
|
const accountId = new AccountId(decoded);
|
||||||
|
|
||||||
|
return idToIndex(api)(accountId).pipe(
|
||||||
|
startWith(undefined),
|
||||||
|
map((accountIndex) => [accountId, accountIndex] as AccountIdAndIndex),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountIndex = new AccountIndex(decoded);
|
||||||
|
|
||||||
|
return indexToId(api)(accountIndex).pipe(
|
||||||
|
startWith(undefined),
|
||||||
|
map((accountId) => [accountId, accountIndex] as AccountIdAndIndex),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return of([undefined, undefined] as AccountIdAndIndex).pipe(drr());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map, startWith } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { AccountId, AccountIndex } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { indexes, AccountIndexes } from './indexes';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
export function idToIndex (api: ApiInterface$Rx) {
|
||||||
|
return (accountId: AccountId | string): Observable<AccountIndex | undefined> =>
|
||||||
|
indexes(api)()
|
||||||
|
.pipe(
|
||||||
|
startWith({}),
|
||||||
|
map((indexes: AccountIndexes) => (indexes || {})[accountId.toString()]),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
export * from './idAndIndex';
|
||||||
|
export * from './idToIndex';
|
||||||
|
export * from './indexToId';
|
||||||
|
export * from './indexes';
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map, startWith } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { ENUMSET_SIZE } from '@plugnet/types/type/AccountIndex';
|
||||||
|
import { AccountId, AccountIndex, Vector } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
export function indexToId (api: ApiInterface$Rx) {
|
||||||
|
return (_accountIndex: AccountIndex | string): Observable<AccountId> => {
|
||||||
|
const querySection = api.query.indices || api.query.balances;
|
||||||
|
const accountIndex = _accountIndex instanceof AccountIndex
|
||||||
|
? _accountIndex
|
||||||
|
: new AccountIndex(_accountIndex);
|
||||||
|
|
||||||
|
return (querySection.enumSet(accountIndex.div(ENUMSET_SIZE)) as Observable<Vector<AccountId>>)
|
||||||
|
.pipe(
|
||||||
|
startWith([]),
|
||||||
|
map((accounts) => (accounts || [])[accountIndex.mod(ENUMSET_SIZE).toNumber()]),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { ENUMSET_SIZE } from '@plugnet/types/type/AccountIndex';
|
||||||
|
import { AccountId, AccountIndex } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
export type AccountIndexes = { [index: string]: AccountIndex };
|
||||||
|
|
||||||
|
export function indexes (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<AccountIndexes> => {
|
||||||
|
const querySection = api.query.indices || api.query.balances;
|
||||||
|
|
||||||
|
return (querySection.nextEnumSet() as Observable<AccountIndex>)
|
||||||
|
.pipe(
|
||||||
|
map((next: AccountIndex) => {
|
||||||
|
const enumRange = [...Array(next.div(ENUMSET_SIZE).toNumber() + 1).keys()];
|
||||||
|
|
||||||
|
return enumRange;
|
||||||
|
}),
|
||||||
|
switchMap((enumRange) => combineLatest(
|
||||||
|
enumRange.map((index) => (querySection.enumSet(index) as Observable<any>))
|
||||||
|
)),
|
||||||
|
map((all: Array<Array<AccountId> | undefined>) =>
|
||||||
|
(all || []).reduce((result, list, outerIndex) => {
|
||||||
|
(list || []).forEach((accountId, innerIndex) => {
|
||||||
|
const index = (outerIndex * ENUMSET_SIZE.toNumber()) + innerIndex;
|
||||||
|
|
||||||
|
result[accountId.toString()] = new AccountIndex(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, {} as AccountIndexes)),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable, combineLatest } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { DerivedFees } from '../types';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
export function fees (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<DerivedFees> => {
|
||||||
|
return (combineLatest(
|
||||||
|
api.query.balances.creationFee(),
|
||||||
|
api.query.balances.existentialDeposit(),
|
||||||
|
api.query.fees
|
||||||
|
? api.query.fees.transactionBaseFee()
|
||||||
|
: api.query.balances.transactionBaseFee(),
|
||||||
|
api.query.fees
|
||||||
|
? api.query.fees.transactionByteFee()
|
||||||
|
: api.query.balances.transactionByteFee(),
|
||||||
|
api.query.balances.transferFee()
|
||||||
|
) as any as Observable<[BN, BN, BN, BN, BN]>).pipe(
|
||||||
|
map(([creationFee, existentialDeposit, transactionBaseFee, transactionByteFee, transferFee]) => ({
|
||||||
|
creationFee,
|
||||||
|
existentialDeposit,
|
||||||
|
transactionBaseFee,
|
||||||
|
transactionByteFee,
|
||||||
|
transferFee
|
||||||
|
})),
|
||||||
|
drr()
|
||||||
|
) ;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
export * from './fees';
|
||||||
|
export * from './validatingBalance';
|
||||||
|
export * from './validatingBalances';
|
||||||
|
export * from './votingBalance';
|
||||||
|
export * from './votingBalances';
|
||||||
|
export * from './votingBalancesNominatorsFor';
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { AccountId, AccountIndex, Address, Balance } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { DerivedBalances } from '../types';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
import { votingBalance } from './votingBalance';
|
||||||
|
import { votingBalancesNominatorsFor } from './votingBalancesNominatorsFor';
|
||||||
|
|
||||||
|
export function validatingBalance (api: ApiInterface$Rx) {
|
||||||
|
return (address: AccountId | AccountIndex | Address | string): Observable<DerivedBalances> => {
|
||||||
|
return combineLatest(
|
||||||
|
votingBalance(api)(address),
|
||||||
|
votingBalancesNominatorsFor(api)(address)
|
||||||
|
).pipe(
|
||||||
|
map(([balance, nominators]) => {
|
||||||
|
const nominatedBalance = nominators.reduce(
|
||||||
|
(total: BN, nominatorBalance: DerivedBalances) =>
|
||||||
|
total.add(nominatorBalance.votingBalance),
|
||||||
|
new BN(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...balance,
|
||||||
|
nominators,
|
||||||
|
nominatedBalance: new Balance(nominatedBalance),
|
||||||
|
stakingBalance: new Balance(
|
||||||
|
nominatedBalance.add(balance.votingBalance)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { combineLatest, Observable, of } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { AccountId, Address } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { DerivedBalancesMap } from '../types';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
import { validatingBalance } from './validatingBalance';
|
||||||
|
|
||||||
|
export function validatingBalances (api: ApiInterface$Rx) {
|
||||||
|
return (accountIds: Array<AccountId | Address | string>): Observable<DerivedBalancesMap> => {
|
||||||
|
return !accountIds || !accountIds.length
|
||||||
|
? of({}).pipe(drr())
|
||||||
|
: combineLatest(
|
||||||
|
accountIds.map(validatingBalance(api))
|
||||||
|
).pipe(
|
||||||
|
map((result) => {
|
||||||
|
return result.reduce((balances, balance) => {
|
||||||
|
balances[balance.accountId.toString()] = balance;
|
||||||
|
|
||||||
|
return balances;
|
||||||
|
}, {} as DerivedBalancesMap);
|
||||||
|
}),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { combineLatest, of, Observable } from 'rxjs';
|
||||||
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { AccountId, AccountIndex, Address, Balance } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { idAndIndex } from '../accounts/idAndIndex';
|
||||||
|
import { DerivedBalances } from '../types';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
const EMPTY_ACCOUNT = new AccountId(new Uint8Array(32));
|
||||||
|
|
||||||
|
export function votingBalance (api: ApiInterface$Rx) {
|
||||||
|
return (address: AccountIndex | AccountId | Address | string): Observable<DerivedBalances> => {
|
||||||
|
return idAndIndex(api)(address).pipe(
|
||||||
|
switchMap(([accountId]) =>
|
||||||
|
(accountId
|
||||||
|
? combineLatest(
|
||||||
|
of(accountId),
|
||||||
|
api.query.balances.freeBalance(accountId),
|
||||||
|
api.query.balances.reservedBalance(accountId)
|
||||||
|
)
|
||||||
|
: of([undefined, undefined, undefined])
|
||||||
|
) as Observable<[AccountId?, Balance?, Balance?]>
|
||||||
|
),
|
||||||
|
map(([accountId = EMPTY_ACCOUNT, freeBalance = new Balance(0), reservedBalance = new Balance(0)]) => {
|
||||||
|
return {
|
||||||
|
accountId,
|
||||||
|
freeBalance,
|
||||||
|
nominatedBalance: new Balance(0),
|
||||||
|
reservedBalance,
|
||||||
|
stakingBalance: new Balance(0),
|
||||||
|
votingBalance: new Balance(
|
||||||
|
freeBalance.add(reservedBalance)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { combineLatest, Observable, of } from 'rxjs';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { AccountId, AccountIndex, Address } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { DerivedBalances } from '../types';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
import { votingBalance } from './votingBalance';
|
||||||
|
|
||||||
|
export function votingBalances (api: ApiInterface$Rx) {
|
||||||
|
return (addresses?: Array<AccountId | AccountIndex | Address | string>): Observable<Array<DerivedBalances>> => {
|
||||||
|
return !addresses || !addresses.length
|
||||||
|
? of([]).pipe(drr())
|
||||||
|
: combineLatest(...addresses.map(votingBalance(api))).pipe(drr());
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { AccountId, AccountIndex, Address, Vector } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { idAndIndex } from '../accounts/idAndIndex';
|
||||||
|
import { DerivedBalances } from '../types';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
import { votingBalances } from './votingBalances';
|
||||||
|
|
||||||
|
export function votingBalancesNominatorsFor (api: ApiInterface$Rx) {
|
||||||
|
return (address: AccountId | AccountIndex | Address | string): Observable<Array<DerivedBalances>> => {
|
||||||
|
return idAndIndex(api)(address).pipe(
|
||||||
|
switchMap(([accountId]) =>
|
||||||
|
accountId
|
||||||
|
? (api.query.staking.nominatorsFor(accountId) as Observable<Vector<AccountId>>)
|
||||||
|
: of([] as Array<AccountId>)
|
||||||
|
),
|
||||||
|
switchMap(votingBalances(api)),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { filter, map } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { BlockNumber, Header } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Get the latest block number.
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* api.derive.chain.bestNumber((blockNumber) => {
|
||||||
|
* console.log(`the current best block is #${blockNumber}`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function bestNumber (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<BlockNumber> =>
|
||||||
|
(api.rpc.chain.subscribeNewHead() as Observable<Header>)
|
||||||
|
.pipe(
|
||||||
|
filter((header: Header) => header && !!header.blockNumber),
|
||||||
|
map(({ blockNumber }: Header) => blockNumber),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { filter, map } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { BlockNumber, Header } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Get the latest finalised block number.
|
||||||
|
* example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* api.derive.chain.bestNumberFinalized((blockNumber) => {
|
||||||
|
* console.log(`the current finalised block is #${blockNumber}`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function bestNumberFinalized (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<BlockNumber> =>
|
||||||
|
(api.rpc.chain.subscribeFinalizedHeads() as Observable<Header>)
|
||||||
|
.pipe(
|
||||||
|
filter((header: Header) => header && !!header.blockNumber),
|
||||||
|
map(({ blockNumber }: Header) => blockNumber),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable, combineLatest } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { BlockNumber } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
import { bestNumber } from './bestNumber';
|
||||||
|
import { bestNumberFinalized } from './bestNumberFinalized';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Calculates the lag between finalised head and best head
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* api.derive.chain.bestNumberLag((lag) => {
|
||||||
|
* console.log(`finalised is ${lag} blocks behind head`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function bestNumberLag (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<BlockNumber> =>
|
||||||
|
combineLatest(
|
||||||
|
bestNumber(api)(),
|
||||||
|
bestNumberFinalized(api)()
|
||||||
|
).pipe(
|
||||||
|
map(([bestNumber, bestNumberFinalized]) =>
|
||||||
|
new BlockNumber(bestNumber.sub(bestNumberFinalized))
|
||||||
|
),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable, combineLatest, of } from 'rxjs';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { Header, HeaderExtended } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
import { HeaderAndValidators } from './subscribeNewHead';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Get the a specific block header and extend it with the author
|
||||||
|
* @param hash: Uint8Array | string
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* const { author, blockNumber } = await api.derive.chain.getHeader('0x123...456');
|
||||||
|
*
|
||||||
|
* console.log(`block #${blockNumber} was authored by ${author}`);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function getHeader (api: ApiInterface$Rx) {
|
||||||
|
return (hash: Uint8Array | string): Observable<HeaderExtended | undefined> =>
|
||||||
|
// tslint:disable-next-line
|
||||||
|
(combineLatest(
|
||||||
|
api.rpc.chain.getHeader(hash) as Observable<Header>,
|
||||||
|
api.query.session
|
||||||
|
? api.query.session.validators.at(hash)
|
||||||
|
: of([])
|
||||||
|
) as Observable<HeaderAndValidators>).pipe(
|
||||||
|
map(([header, validators]) =>
|
||||||
|
new HeaderExtended(header, validators)
|
||||||
|
),
|
||||||
|
catchError(() =>
|
||||||
|
// where rpc.chain.getHeader throws, we will land here - it can happen that
|
||||||
|
// we supplied an invalid hash. (Due to defaults, storeage will have an
|
||||||
|
// empty value, so only the RPC is affected). So return undefined
|
||||||
|
// tslint:disable-next-line
|
||||||
|
of() as Observable<undefined>
|
||||||
|
),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
export * from './bestNumber';
|
||||||
|
export * from './bestNumberFinalized';
|
||||||
|
export * from './bestNumberLag';
|
||||||
|
export * from './getHeader';
|
||||||
|
export * from './subscribeNewHead';
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable, combineLatest, of } from 'rxjs';
|
||||||
|
import { filter, map, switchMap } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { AccountId, Header, HeaderExtended } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
export type HeaderAndValidators = [Header, Array<AccountId>];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Subscribe to block headers and extend it with the author
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* api.derive.chain.subscribeNewHead(({ author, blockNumber }) => {
|
||||||
|
* console.log(`block #${blockNumber} was authored by ${author}`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function subscribeNewHead (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<HeaderExtended> =>
|
||||||
|
(api.rpc.chain.subscribeNewHead() as Observable<Header>)
|
||||||
|
.pipe(
|
||||||
|
filter((header: Header) =>
|
||||||
|
header && !!header.blockNumber
|
||||||
|
),
|
||||||
|
switchMap((header: Header): Observable<HeaderAndValidators> =>
|
||||||
|
(combineLatest(
|
||||||
|
of(header),
|
||||||
|
// theoretically we could combine at the first call with session.validators(), however
|
||||||
|
// we make 100% sure we actually get the validators at a specific block so when these
|
||||||
|
// change at an era boundary, we have the previous values to ensure our indexes are correct
|
||||||
|
api.query.session
|
||||||
|
? api.query.session.validators.at(header.hash)
|
||||||
|
: of([])
|
||||||
|
) as Observable<HeaderAndValidators>)
|
||||||
|
),
|
||||||
|
map(([header, validators]) =>
|
||||||
|
new HeaderExtended(header, validators)
|
||||||
|
),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
export * from './referendumInfos';
|
||||||
|
export * from './referendums';
|
||||||
|
export * from './referendumVotesFor';
|
||||||
|
export * from './votes';
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { Option, ReferendumInfo, ReferendumIndex } from '@plugnet/types';
|
||||||
|
import { isNull } from '@plugnet/util';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name ReferendumInfoExtended
|
||||||
|
* @description
|
||||||
|
* A [[ReferendumInfo]] with an additional `index` field
|
||||||
|
*/
|
||||||
|
export class ReferendumInfoExtended extends ReferendumInfo {
|
||||||
|
private _index: ReferendumIndex;
|
||||||
|
|
||||||
|
constructor (value: ReferendumInfo | ReferendumInfoExtended, index?: BN | number) {
|
||||||
|
super(value);
|
||||||
|
|
||||||
|
this._index = value instanceof ReferendumInfoExtended
|
||||||
|
? value.index
|
||||||
|
: new ReferendumIndex(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Convenience getter, returns the referendumIndex
|
||||||
|
*/
|
||||||
|
get index (): ReferendumIndex {
|
||||||
|
return this._index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Creates the JSON representation
|
||||||
|
*/
|
||||||
|
toJSON (): any {
|
||||||
|
return {
|
||||||
|
...super.toJSON(),
|
||||||
|
index: this.index.toJSON()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function referendumInfo (api: ApiInterface$Rx) {
|
||||||
|
return (index: BN | number): Observable<Option<ReferendumInfoExtended>> => {
|
||||||
|
return (api.query.democracy.referendumInfoOf(index) as Observable<Option<ReferendumInfo>>)
|
||||||
|
.pipe(
|
||||||
|
map((optionInfo) => {
|
||||||
|
const info = optionInfo.unwrapOr(null);
|
||||||
|
|
||||||
|
return new Option<ReferendumInfoExtended>(
|
||||||
|
ReferendumInfoExtended,
|
||||||
|
isNull(info)
|
||||||
|
? null
|
||||||
|
: new ReferendumInfoExtended(info, index)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { combineLatest, Observable, of } from 'rxjs';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { Option } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
import { referendumInfo, ReferendumInfoExtended } from './referendumInfo';
|
||||||
|
|
||||||
|
export function referendumInfos (api: ApiInterface$Rx) {
|
||||||
|
const referendumInfoOf = referendumInfo(api);
|
||||||
|
|
||||||
|
return (ids: Array<BN | number> = []): Observable<Array<Option<ReferendumInfoExtended>>> => {
|
||||||
|
return !ids || !ids.length
|
||||||
|
? of([]).pipe(drr())
|
||||||
|
: combineLatest(
|
||||||
|
ids.map((id) => referendumInfoOf(id))
|
||||||
|
).pipe(
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { combineLatest, Observable, of } from 'rxjs';
|
||||||
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { AccountId, Balance, Vector, Vote } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { DerivedReferendumVote } from '../types';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
import { votes } from './votes';
|
||||||
|
import { votingBalances } from '../balances/votingBalances';
|
||||||
|
|
||||||
|
export function referendumVotesFor (api: ApiInterface$Rx) {
|
||||||
|
return (referendumId: BN | number): Observable<Array<DerivedReferendumVote>> =>
|
||||||
|
(api.query.democracy.votersFor(referendumId) as Observable<Vector<AccountId>>).pipe(
|
||||||
|
switchMap((votersFor) =>
|
||||||
|
combineLatest(
|
||||||
|
of(votersFor),
|
||||||
|
votes(api)(referendumId as BN, votersFor),
|
||||||
|
votingBalances(api)(votersFor)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
map(([votersFor, votes, balances]) =>
|
||||||
|
votersFor.map((accountId, index): DerivedReferendumVote => ({
|
||||||
|
accountId,
|
||||||
|
balance: balances[index].votingBalance || new Balance(0),
|
||||||
|
vote: votes[index] || new Vote(0)
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { combineLatest, Observable, of } from 'rxjs';
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { Option, ReferendumIndex } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
import { ReferendumInfoExtended } from './referendumInfo';
|
||||||
|
import { referendumInfos } from './referendumInfos';
|
||||||
|
|
||||||
|
export function referendums (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<Array<Option<ReferendumInfoExtended>>> =>
|
||||||
|
(combineLatest(
|
||||||
|
api.query.democracy.nextTally(),
|
||||||
|
api.query.democracy.referendumCount()
|
||||||
|
) as Observable<[ReferendumIndex?, ReferendumIndex?]>).pipe(
|
||||||
|
switchMap(([nextTally, referendumCount]) =>
|
||||||
|
referendumCount && nextTally && referendumCount.gt(nextTally) && referendumCount.gtn(0)
|
||||||
|
? referendumInfos(api)(
|
||||||
|
[...Array(referendumCount.sub(nextTally).toNumber())].map((_, i) =>
|
||||||
|
nextTally.addn(i)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: of([])
|
||||||
|
),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { combineLatest, Observable, of } from 'rxjs';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { AccountId, Vote } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
export function votes (api: ApiInterface$Rx) {
|
||||||
|
return (referendumId: BN, accountIds: Array<AccountId> = []): Observable<Array<Vote>> => {
|
||||||
|
return !accountIds || !accountIds.length
|
||||||
|
? of([]).pipe(drr())
|
||||||
|
: combineLatest(
|
||||||
|
accountIds.map(
|
||||||
|
(accountId) => api.query.democracy.voteOf([referendumId, accountId]) as Observable<Vote>
|
||||||
|
)
|
||||||
|
).pipe(
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable, from } from 'rxjs';
|
||||||
|
import ApiRx from '@plugnet/api/rx/Api';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import MockProvider from '@plugnet/rpc-provider/mock';
|
||||||
|
|
||||||
|
import { Derive } from '.';
|
||||||
|
|
||||||
|
const testFunction = (api: ApiInterface$Rx) => {
|
||||||
|
return <
|
||||||
|
Section extends keyof Derive,
|
||||||
|
Method extends keyof (typeof api.derive[Section])
|
||||||
|
>(section: Section, method: Method, inputs: any[]) => {
|
||||||
|
describe(`derive.${section}.${method}`, () => {
|
||||||
|
it('should be a function', () => {
|
||||||
|
expect(typeof api.derive[section][method]).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an Observable', () => {
|
||||||
|
expect((api.derive[section][method] as any)(...inputs)).toBeInstanceOf(Observable);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('derive', () => {
|
||||||
|
describe('builtin', () => {
|
||||||
|
const api = new ApiRx(new MockProvider());
|
||||||
|
|
||||||
|
beforeAll((done) => {
|
||||||
|
api.isReady.subscribe(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
testFunction(api)('accounts', 'idAndIndex', []);
|
||||||
|
testFunction(api)('accounts', 'idToIndex', []);
|
||||||
|
testFunction(api)('accounts', 'indexes', []);
|
||||||
|
testFunction(api)('accounts', 'indexToId', []);
|
||||||
|
|
||||||
|
testFunction(api)('balances', 'fees', []);
|
||||||
|
testFunction(api)('balances', 'validatingBalance', []);
|
||||||
|
testFunction(api)('balances', 'validatingBalances', []);
|
||||||
|
testFunction(api)('balances', 'votingBalance', []);
|
||||||
|
testFunction(api)('balances', 'votingBalances', []);
|
||||||
|
testFunction(api)('balances', 'votingBalancesNominatorsFor', []);
|
||||||
|
|
||||||
|
testFunction(api)('chain', 'bestNumber', []);
|
||||||
|
testFunction(api)('chain', 'bestNumberFinalized', []);
|
||||||
|
|
||||||
|
testFunction(api)('democracy', 'referendumInfos', []);
|
||||||
|
testFunction(api)('democracy', 'referendums', []);
|
||||||
|
testFunction(api)('democracy', 'referendumVotesFor', []);
|
||||||
|
testFunction(api)('democracy', 'votes', []);
|
||||||
|
|
||||||
|
testFunction(api)('session', 'eraLength', []);
|
||||||
|
testFunction(api)('session', 'eraProgress', []);
|
||||||
|
testFunction(api)('session', 'sessionProgress', []);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('custom', () => {
|
||||||
|
const api = new ApiRx({
|
||||||
|
derives: {
|
||||||
|
balances: {
|
||||||
|
fees: () => () => from(['a', 'b'])
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
test: () => () => from([1, 2, 3])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
provider: new MockProvider()
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeAll((done) => {
|
||||||
|
api.isReady.subscribe(() => done());
|
||||||
|
});
|
||||||
|
|
||||||
|
// override
|
||||||
|
testFunction(api)('balances', 'fees', ['a', 'b']);
|
||||||
|
|
||||||
|
// new
|
||||||
|
testFunction(api)('custom' as any, 'test', [1, 2, 3]);
|
||||||
|
|
||||||
|
// existing
|
||||||
|
testFunction(api)('chain', 'bestNumber', []);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
|
||||||
|
import * as accounts from './accounts';
|
||||||
|
import * as balances from './balances';
|
||||||
|
import * as chain from './chain';
|
||||||
|
import * as democracy from './democracy';
|
||||||
|
import * as session from './session';
|
||||||
|
import * as staking from './staking';
|
||||||
|
|
||||||
|
// Put all derived functions in an object, for easier Object.keys()-ing.
|
||||||
|
const functions = { accounts, balances, chain, democracy, session, staking };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* T represents the section here (chain, balances...), and P represents
|
||||||
|
* the function name (bestNumber, sessionProgress...).
|
||||||
|
*/
|
||||||
|
type ReturnTypes<T extends Record<keyof T, (...args: any[]) => any>> = {
|
||||||
|
[P in keyof T]: ReturnType<T[P]>
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface DeriveCustomMethod {
|
||||||
|
(api: ApiInterface$Rx): (...args: Array<any>) => Observable<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeriveCustom {
|
||||||
|
[index: string]: {
|
||||||
|
[index: string]: DeriveCustomMethod
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Derive {
|
||||||
|
accounts: ReturnTypes<typeof accounts>;
|
||||||
|
balances: ReturnTypes<typeof balances>;
|
||||||
|
chain: ReturnTypes<typeof chain>;
|
||||||
|
democracy: ReturnTypes<typeof democracy>;
|
||||||
|
session: ReturnTypes<typeof session>;
|
||||||
|
staking: ReturnTypes<typeof staking>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectFunctions (api: ApiInterface$Rx, derive: Derive, functions: DeriveCustom): Derive {
|
||||||
|
Object.keys(functions).forEach((sectionName: string) => {
|
||||||
|
const section = functions[sectionName as keyof Derive];
|
||||||
|
const result = derive[sectionName as keyof Derive] = derive[sectionName as keyof Derive] || {};
|
||||||
|
|
||||||
|
Object.keys(section).forEach((methodName) => {
|
||||||
|
// @ts-ignore No idea how to make this work...
|
||||||
|
result[methodName as keyof section] = section[methodName as keyof section](api);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return derive;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function decorateDerive (api: ApiInterface$Rx, custom: DeriveCustom = {}): Derive {
|
||||||
|
const derive = {} as Derive;
|
||||||
|
|
||||||
|
injectFunctions(api, derive, functions);
|
||||||
|
injectFunctions(api, derive, custom);
|
||||||
|
|
||||||
|
return derive;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { BlockNumber } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
export function eraLength (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<BN> =>
|
||||||
|
(combineLatest(
|
||||||
|
api.query.session.sessionLength(),
|
||||||
|
api.query.staking.sessionsPerEra()
|
||||||
|
) as Observable<[BlockNumber?, BlockNumber?]>).pipe(
|
||||||
|
map(
|
||||||
|
([sessionLength, sessionsPerEra]) =>
|
||||||
|
(sessionLength || new BN(1)).mul(sessionsPerEra || new BN(1))
|
||||||
|
),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { BlockNumber } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { sessionProgress } from './sessionProgress';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
export function eraProgress (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<BN> =>
|
||||||
|
(combineLatest(
|
||||||
|
sessionProgress(api)(),
|
||||||
|
api.query.session.currentIndex(),
|
||||||
|
api.query.session.sessionLength(),
|
||||||
|
api.query.staking.lastEraLengthChange(),
|
||||||
|
api.query.staking.sessionsPerEra()
|
||||||
|
) as Observable<[BN, BlockNumber?, BlockNumber?, BlockNumber?, BlockNumber?]>).pipe(
|
||||||
|
map(
|
||||||
|
([sessionProgress, currentIndex, sessionLength, lastEraLengthChange, sessionsPerEra]) =>
|
||||||
|
(currentIndex || new BN(0))
|
||||||
|
.sub(lastEraLengthChange || new BN(0))
|
||||||
|
.mod(sessionsPerEra || new BN(1))
|
||||||
|
.mul(sessionLength || new BN(1))
|
||||||
|
.add(sessionProgress || new BN(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
export * from './eraLength';
|
||||||
|
export * from './eraProgress';
|
||||||
|
export * from './sessionProgress';
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { BlockNumber, Option } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { bestNumber } from '../chain';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
export function sessionProgress (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<BN> =>
|
||||||
|
combineLatest(
|
||||||
|
bestNumber(api)(),
|
||||||
|
api.query.session.sessionLength(),
|
||||||
|
api.query.session.lastLengthChange()
|
||||||
|
).pipe(
|
||||||
|
map(
|
||||||
|
([bestNumber, sessionLength, lastLengthChange]) =>
|
||||||
|
(bestNumber || new BN(0))
|
||||||
|
.sub((lastLengthChange as Option<BlockNumber>).unwrapOr(new BN(0)))
|
||||||
|
.add(sessionLength as BlockNumber || new BN(1))
|
||||||
|
.mod(sessionLength as BlockNumber || new BN(1))
|
||||||
|
),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable, combineLatest, of } from 'rxjs';
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { AccountId, Option } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
function allBonds (api: ApiInterface$Rx, stashIds: Array<AccountId>) {
|
||||||
|
return combineLatest(
|
||||||
|
stashIds.map((id) =>
|
||||||
|
(api.query.staking.bonded(id) as Observable<Option<AccountId>>)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description From the list of stash accounts, retrieve the list of controllers
|
||||||
|
*/
|
||||||
|
export function controllers (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<[Array<AccountId>, Array<Option<AccountId>>]> =>
|
||||||
|
(api.query.staking.validators() as any as Observable<[Array<AccountId>, any]>)
|
||||||
|
.pipe(
|
||||||
|
switchMap(([stashIds]) =>
|
||||||
|
combineLatest(
|
||||||
|
of(stashIds),
|
||||||
|
allBonds(api, stashIds)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
drr()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
export * from './controllers';
|
||||||
|
export * from './intentionsBalances';
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { AccountId, Vector } from '@plugnet/types';
|
||||||
|
|
||||||
|
import { DerivedBalancesMap } from '../types';
|
||||||
|
import { validatingBalances } from '../balances';
|
||||||
|
import { drr } from '../util/drr';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the balances for all intentions and their nominators
|
||||||
|
*/
|
||||||
|
export function intentionsBalances (api: ApiInterface$Rx) {
|
||||||
|
return (): Observable<DerivedBalancesMap> =>
|
||||||
|
// tslint:disable-next-line
|
||||||
|
(api.query.staking.intentions() as Observable<Vector<AccountId>>)
|
||||||
|
.pipe(
|
||||||
|
switchMap(validatingBalances(api)),
|
||||||
|
drr()
|
||||||
|
) as Observable<DerivedBalancesMap>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import { AccountId, Balance, Vote } from '@plugnet/types';
|
||||||
|
|
||||||
|
export type DerivedBalances = {
|
||||||
|
accountId: AccountId,
|
||||||
|
freeBalance: Balance,
|
||||||
|
nominatedBalance: Balance,
|
||||||
|
reservedBalance: Balance,
|
||||||
|
votingBalance: Balance,
|
||||||
|
stakingBalance: Balance,
|
||||||
|
nominators?: Array<DerivedBalances>
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DerivedFees = {
|
||||||
|
creationFee: BN,
|
||||||
|
existentialDeposit: BN,
|
||||||
|
transactionBaseFee: BN,
|
||||||
|
transactionByteFee: BN,
|
||||||
|
transferFee: BN
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DerivedBalancesMap = {
|
||||||
|
[index: string]: DerivedBalances
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DerivedReferendumVote = {
|
||||||
|
accountId: AccountId,
|
||||||
|
balance: Balance,
|
||||||
|
vote: Vote
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { of, timer } from 'rxjs';
|
||||||
|
|
||||||
|
import { drr } from './drr';
|
||||||
|
|
||||||
|
describe('drr', () => {
|
||||||
|
it('should not fire twice the same value', (done) => {
|
||||||
|
let count = 0;
|
||||||
|
of(1, 1).pipe(drr()).subscribe(() => { ++count; });
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(count).toBe(1);
|
||||||
|
done();
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be a ReplaySubject(1)', (done) => {
|
||||||
|
const obs = timer(0, 100).pipe(drr()); // Starts at 0, increments every 100ms
|
||||||
|
obs.subscribe(); // Fire the observable
|
||||||
|
|
||||||
|
// Subscribe another time after some time, i.e. after the observable has fired
|
||||||
|
setTimeout(() => {
|
||||||
|
obs.subscribe((value) => {
|
||||||
|
expect(value > 1).toBe(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { distinctUntilChanged, publishReplay, refCount } from 'rxjs/operators';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand for distinctUntilChanged(), publishReplay(1) and refCount().
|
||||||
|
*
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export const drr = () => <T>(source$: Observable<T>): Observable<T> =>
|
||||||
|
source$.pipe(
|
||||||
|
distinctUntilChanged(),
|
||||||
|
publishReplay(1),
|
||||||
|
refCount()
|
||||||
|
);
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import ApiPromise from '@plugnet/api/promise/Api';
|
||||||
|
import { BlockNumber } from '@plugnet/types';
|
||||||
|
import { WsProvider } from '@plugnet/rpc-provider';
|
||||||
|
|
||||||
|
const WS_LOCAL = 'ws://127.0.0.1:9944/';
|
||||||
|
// const WS_POC3 = 'wss://poc3-rpc.polkadot.io/';
|
||||||
|
|
||||||
|
describe.skip('derive e2e', () => {
|
||||||
|
let api: ApiPromise;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
api = await ApiPromise.create(new WsProvider(WS_LOCAL));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct results', async () => {
|
||||||
|
// https://github.com/polkadot-js/api/issues/777
|
||||||
|
const block1 = await api.derive.chain.bestNumber();
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 15000));
|
||||||
|
const block2 = await api.derive.chain.bestNumber();
|
||||||
|
|
||||||
|
expect((block1 as BlockNumber).eq(block2)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import ApiRx from '@plugnet/api/rx/Api';
|
||||||
|
import { ApiInterface$Rx } from '@plugnet/api/types';
|
||||||
|
import { BlockNumber } from '@plugnet/types';
|
||||||
|
import { WsProvider } from '@plugnet/rpc-provider';
|
||||||
|
|
||||||
|
const WS_LOCAL = 'ws://127.0.0.1:9944/';
|
||||||
|
// const WS_POC3 = 'wss://poc3-rpc.polkadot.io/';
|
||||||
|
|
||||||
|
describe.skip('derive e2e', () => {
|
||||||
|
let api: ApiInterface$Rx;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.setTimeout(10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
api = await ApiRx.create(new WsProvider(WS_LOCAL)).toPromise();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('derive.chain.bestNumber', async (done) => {
|
||||||
|
api.derive.chain.bestNumber().subscribe((blockNumber) => {
|
||||||
|
expect(blockNumber instanceof BlockNumber).toBe(true);
|
||||||
|
expect((blockNumber as BlockNumber).gten(0)).toBe(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('derive.session.sessionProgress', async (done) => {
|
||||||
|
api.derive.session.sessionProgress().subscribe((progress) => {
|
||||||
|
expect(progress instanceof BN).toBe(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the intentions with balances', async (done) => {
|
||||||
|
api.derive.staking.intentionsBalances().subscribe((balances) => {
|
||||||
|
expect(Object.keys(balances as object)).not.toHaveLength(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// these only work on localhost, not the poc-3 URL
|
||||||
|
// (and it is assuming it sent at least 1 tx)
|
||||||
|
describe('accounts', () => {
|
||||||
|
const ID = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY';
|
||||||
|
const IX = 'F7Gh';
|
||||||
|
|
||||||
|
it('looks up id & index from id', async (done) => {
|
||||||
|
// @ts-ignore silence warning until we have static types here
|
||||||
|
api.derive.accounts.idAndIndex(ID).subscribe(([id, ix]) => {
|
||||||
|
expect(id.toString()).toEqual(ID);
|
||||||
|
expect(ix.toString()).toEqual(IX);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('looks up id & index from index', async (done) => {
|
||||||
|
// @ts-ignore silence warning until we have static types here
|
||||||
|
api.derive.accounts.idAndIndex(IX).subscribe(([id, ix]) => {
|
||||||
|
expect(id.toString()).toEqual(ID);
|
||||||
|
expect(ix.toString()).toEqual(IX);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.setTimeout(5000);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# @polkadot/api
|
||||||
|
|
||||||
|
The Polkadot-JS API provides easy-to-use wrappers around JSONRPC calls that flow from an application to a node. It handles all the encoding and decoding or parameters, provides access to RPC functions and allows for the query of chain state and the submission of transactions.
|
||||||
|
|
||||||
|
The API wrappers provide a standard interface for use -
|
||||||
|
|
||||||
|
- A static `.create(<optional ApiOptions>)` that returns an API instance when connected, decorated and ready-to use. ApiOptions can include an optional WsProvider and optional custom type definitions `{ provider: <Optional WsProvider>, types: <Optional RegistryTypes> }`.
|
||||||
|
- The above is just a wrapper for `new Api(<optional ApiOptions>) `, exposing the `isReady` getter
|
||||||
|
- `api.rpc.<section>.<method>` provides access to actual RPC calls, be it for queries, submission or retrieving chain information
|
||||||
|
- [RPC (node interface)](../METHODS_RPC.md)
|
||||||
|
- `api.query.<section>.<method>` provides access to chain state queries. These are dynamically populated based on what the runtime provides
|
||||||
|
- [Storage chain state (runtime node interface)](../METHODS_STORAGE.md)
|
||||||
|
- `api.tx.<section>.<method>` provides the ability to create a transaction, like chain state, this list is populated from a runtime query
|
||||||
|
- [Extrinsics (runtime node interface)](../METHODS_EXTRINSICS.md)
|
||||||
|
|
||||||
|
## API Selection
|
||||||
|
|
||||||
|
There are two flavours of the API provided, one allowing a standard interface via JavaScript Promises and the second provides an Observable wrapper using [RxJS](https://github.com/ReactiveX/rxjs). Depending on your use-case and familiarity, you can choose either (or even both) for your application.
|
||||||
|
|
||||||
|
- [[ApiPromise]] All interface calls returns Promises, including the static `.create(...)`. Additionally any subscription method uses `(value) => {}` callbacks, returning the value as the subscription is updated.
|
||||||
|
- [[ApiRx]] All interface calls return RxJS Observables, including the static `.create(...)`. In the same fashion subscription-based methods return long-running Observables that update with the latest values.
|
||||||
|
|
||||||
|
## Dynamic by default
|
||||||
|
|
||||||
|
Substrate (upon which Polkadot is built) uses on-chain WASM runtimes, allowing for upgradability. Each runtime defining the actual chain extrinsics (submitted transactions and block intrinsics) as well as available entries in the chain state. Due to this, the API endpoints for queries and transactions are dynamically populated from the running chain.
|
||||||
|
|
||||||
|
Due to this dynamic nature, this API departs from traditional APIs which only has fixed endpoints, driving use by what is available by the runtime. As a start, this generic nature has a learning curve, although the provided documentation, examples and linked documentation tries to make that experience as seamless as possible.
|
||||||
|
|
||||||
|
## Installation & import
|
||||||
|
|
||||||
|
Installation -
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install --save @polkadot/api
|
||||||
|
```
|
||||||
|
|
||||||
|
Subscribing to blocks via Promise-based API -
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { ApiPromise } from '@polkadot/api';
|
||||||
|
|
||||||
|
// initialise via static create
|
||||||
|
const api = await ApiPromise.create();
|
||||||
|
|
||||||
|
// make a call to retrieve the current network head
|
||||||
|
api.rpc.chain.subscribeNewHead((header) => {
|
||||||
|
console.log(`Chain is at #${header.blockNumber}`);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Subscribing to blocks via RxJS-based API -
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { ApiRx } from '@polkadot/api';
|
||||||
|
|
||||||
|
// initialise via static create
|
||||||
|
const api = await ApiRx.create().toPromise();
|
||||||
|
|
||||||
|
// make a call to retrieve the current network head
|
||||||
|
api.rpc.chain.subscribeNewHead().subscribe((header) => {
|
||||||
|
console.log(`Chain is at #${header.blockNumber}`);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Registering custom types
|
||||||
|
|
||||||
|
Additional types used by runtime modules can be added when a new instance of the API is created. This is necessary if the runtime modules use types which are not available in the base Substrate runtime.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { ApiPromise } from '@polkadot/api';
|
||||||
|
|
||||||
|
// initialise via static create and register custom types
|
||||||
|
const api = await ApiPromise.create({
|
||||||
|
types: {
|
||||||
|
CustomTypesExample: {
|
||||||
|
"id": "u32",
|
||||||
|
"data": "Vec<u8>",
|
||||||
|
"deposit": "Balance",
|
||||||
|
"owner": "AccountId",
|
||||||
|
"application_expiry": "Moment",
|
||||||
|
"whitelisted": "bool",
|
||||||
|
"challenge_id": "u32"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Users
|
||||||
|
|
||||||
|
Some of the users of the API (let us know if you are missing from the list), include -
|
||||||
|
|
||||||
|
- [Polkadot-JS UI](https://github.com/polkadot-js/apps) A user-interface that allows you to make transactions, query the network or participate in actions on the network such as referendums and staking
|
||||||
|
- [Polkabot](https://gitlab.com/Polkabot) Polkabot is a Matrix chatbot that keeps an eye on the Polkadot network. You can see Polkabot in action in https://matrix.to/#/#polkadot-network-status:matrix.org
|
||||||
|
- [Polkawallet.io](https://polkawallet.io) and [Polkawallet (Github)](https://github.com/polkawallet-io/polkawallet-RN/) A mobile wallet for the Polkadot network to manage funds and make transactions, available on both Androind and iOS
|
||||||
|
|
||||||
|
## Classes
|
||||||
|
|
||||||
|
[Classes](SUMMARY.md)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "@plugnet/api",
|
||||||
|
"version": "0.76.1",
|
||||||
|
"description": "Promise and RxJS wrappers around the Polkadot JS RPC",
|
||||||
|
"main": "index.js",
|
||||||
|
"keywords": [
|
||||||
|
"Polkadot",
|
||||||
|
"RxJs"
|
||||||
|
],
|
||||||
|
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||||
|
"maintainers": [
|
||||||
|
"Jaco Greeff <jacogr@gmail.com>"
|
||||||
|
],
|
||||||
|
"contributors": [],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/polkadot-js/api.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/polkadot-js/api/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/api#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.4.3",
|
||||||
|
"@plugnet/api-derive": "^0.76.1",
|
||||||
|
"@plugnet/extrinsics": "^0.76.1",
|
||||||
|
"@plugnet/rpc-provider": "^0.76.1",
|
||||||
|
"@plugnet/rpc-rx": "^0.76.1",
|
||||||
|
"@plugnet/storage": "^0.76.1",
|
||||||
|
"@plugnet/types": "^0.76.1",
|
||||||
|
"@plugnet/util-crypto": "^0.76.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,667 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { ProviderInterface } from '@plugnet/rpc-provider/types';
|
||||||
|
import { Storage } from '@plugnet/storage/types';
|
||||||
|
import { Codec, CodecArg, CodecCallback, RegistryTypes } from '@plugnet/types/types';
|
||||||
|
import { RxResult } from './rx/types';
|
||||||
|
import {
|
||||||
|
ApiBaseInterface, ApiInterface$Rx, ApiInterface$Events, ApiOptions, ApiType,
|
||||||
|
DecoratedRpc, DecoratedRpc$Method, DecoratedRpc$Section,
|
||||||
|
Derive, DeriveSection, HashResult, U64Result,
|
||||||
|
OnCallDefinition, OnCallFunction,
|
||||||
|
QueryableModuleStorage, QueryableStorage, QueryableStorageFunction,
|
||||||
|
SubmittableExtrinsicFunction, SubmittableExtrinsics, SubmittableModuleExtrinsics, Signer
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
import EventEmitter from 'eventemitter3';
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
|
import decorateDerive, { Derive as DeriveInterface } from '@plugnet/api-derive';
|
||||||
|
import extrinsicsFromMeta from '@plugnet/extrinsics/fromMetadata';
|
||||||
|
import RpcBase from '@plugnet/rpc-core';
|
||||||
|
import RpcRx from '@plugnet/rpc-rx';
|
||||||
|
import storageFromMeta from '@plugnet/storage/fromMetadata';
|
||||||
|
import { Event, getTypeRegistry, Hash, Metadata, Method, RuntimeVersion, Null } from '@plugnet/types';
|
||||||
|
import { Linkage, LinkageResult } from '@plugnet/types/codec/Linkage';
|
||||||
|
import { MethodFunction, ModulesWithMethods } from '@plugnet/types/primitive/Method';
|
||||||
|
import { StorageFunction } from '@plugnet/types/primitive/StorageKey';
|
||||||
|
import { assert, compactStripLength, isFunction, isObject, isUndefined, logger, u8aToHex } from '@plugnet/util';
|
||||||
|
import { cryptoWaitReady } from '@plugnet/util-crypto';
|
||||||
|
|
||||||
|
import createSubmittable, { SubmittableExtrinsic } from './SubmittableExtrinsic';
|
||||||
|
|
||||||
|
type MetaDecoration = {
|
||||||
|
callIndex?: Uint8Array,
|
||||||
|
meta: any,
|
||||||
|
method: string,
|
||||||
|
section: string,
|
||||||
|
toJSON: () => any
|
||||||
|
};
|
||||||
|
|
||||||
|
const INIT_ERROR = `Api needs to be initialised before using, listen on 'ready'`;
|
||||||
|
const KEEPALIVE_INTERVAL = 15000;
|
||||||
|
|
||||||
|
const l = logger('api/decorator');
|
||||||
|
|
||||||
|
let pkgJson: { name: string, version: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
pkgJson = require('./package.json');
|
||||||
|
} catch (error) {
|
||||||
|
// development environment
|
||||||
|
pkgJson = { name: '@polkadot/api', version: '-' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put the `this.onCall` function of ApiRx here, because it is needed by
|
||||||
|
* `api._rx`.
|
||||||
|
*/
|
||||||
|
function rxOnCall (
|
||||||
|
method: OnCallFunction<RxResult, RxResult>,
|
||||||
|
params: Array<CodecArg> = [],
|
||||||
|
_callback?: CodecCallback
|
||||||
|
): RxResult {
|
||||||
|
return method(...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default abstract class ApiBase<CodecResult, SubscriptionResult> implements ApiBaseInterface<CodecResult, SubscriptionResult> {
|
||||||
|
private _derive?: Derive<CodecResult, SubscriptionResult>;
|
||||||
|
private _eventemitter: EventEmitter;
|
||||||
|
private _extrinsics?: SubmittableExtrinsics<CodecResult, SubscriptionResult>;
|
||||||
|
private _genesisHash?: Hash;
|
||||||
|
private _isReady: boolean = false;
|
||||||
|
protected readonly _options: ApiOptions;
|
||||||
|
private _query?: QueryableStorage<CodecResult, SubscriptionResult>;
|
||||||
|
private _rpc: DecoratedRpc<CodecResult, SubscriptionResult>;
|
||||||
|
protected _rpcBase: RpcBase; // FIXME These two could be merged
|
||||||
|
protected _rpcRx: RpcRx; // FIXME These two could be merged
|
||||||
|
private _runtimeMetadata?: Metadata;
|
||||||
|
private _runtimeVersion?: RuntimeVersion;
|
||||||
|
private _rx: Partial<ApiInterface$Rx> = {};
|
||||||
|
private _type: ApiType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Create an instance of the class
|
||||||
|
*
|
||||||
|
* @param options Options object to create API instance or a Provider instance
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* import Api from '@plugnet/api/promise';
|
||||||
|
*
|
||||||
|
* const api = new Api().isReady();
|
||||||
|
*
|
||||||
|
* api.rpc.subscribeNewHead((header) => {
|
||||||
|
* console.log(`new block #${header.blockNumber.toNumber()}`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
constructor (provider: ApiOptions | ProviderInterface = {}, type: ApiType) {
|
||||||
|
const options = isObject(provider) && isFunction((provider as ProviderInterface).send)
|
||||||
|
? { provider } as ApiOptions
|
||||||
|
: provider as ApiOptions;
|
||||||
|
const thisProvider = options.source
|
||||||
|
? options.source._rpcBase._provider.clone()
|
||||||
|
: options.provider;
|
||||||
|
|
||||||
|
this._options = options;
|
||||||
|
this._type = type;
|
||||||
|
this._eventemitter = new EventEmitter();
|
||||||
|
this._rpcBase = new RpcBase(thisProvider);
|
||||||
|
|
||||||
|
assert(this.hasSubscriptions, 'Api can only be used with a provider supporting subscriptions');
|
||||||
|
|
||||||
|
this._rpcRx = new RpcRx(thisProvider);
|
||||||
|
this._rpc = this.decorateRpc(this._rpcRx, this.onCall) as any; // FIXME 3.4.1
|
||||||
|
this._rx.rpc = this.decorateRpc(this._rpcRx, rxOnCall);
|
||||||
|
this._rx.signer = options.signer;
|
||||||
|
|
||||||
|
// we only re-register the types (global) if this is not a cloned instance
|
||||||
|
if (!options.source) {
|
||||||
|
this.registerTypes(options.types);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Contains the genesis Hash of the attached chain. Apart from being useful to determine the actual chain, it can also be used to sign immortal transactions.
|
||||||
|
*/
|
||||||
|
get genesisHash (): Hash {
|
||||||
|
assert(!isUndefined(this._genesisHash), INIT_ERROR);
|
||||||
|
|
||||||
|
return this._genesisHash as Hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description `true` when subscriptions are supported
|
||||||
|
*/
|
||||||
|
get hasSubscriptions (): boolean {
|
||||||
|
return this._rpcBase._provider.hasSubscriptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description The library information name & version (from package.json)
|
||||||
|
*/
|
||||||
|
get libraryInfo (): string {
|
||||||
|
return `${pkgJson.name} v${pkgJson.version}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Yields the current attached runtime metadata. Generally this is only used to construct extrinsics & storage, but is useful for current runtime inspection.
|
||||||
|
*/
|
||||||
|
get runtimeMetadata (): Metadata {
|
||||||
|
assert(!isUndefined(this._runtimeMetadata), INIT_ERROR);
|
||||||
|
|
||||||
|
return this._runtimeMetadata as Metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Contains the version information for the current runtime.
|
||||||
|
*/
|
||||||
|
get runtimeVersion (): RuntimeVersion {
|
||||||
|
assert(!isUndefined(this._runtimeVersion), INIT_ERROR);
|
||||||
|
|
||||||
|
return this._runtimeVersion as RuntimeVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description The type of this API instance, either 'rxjs' or 'promise'
|
||||||
|
*/
|
||||||
|
get type (): ApiType {
|
||||||
|
return this._type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Set an external signer which will be used to sign extrinsic when account passed in is not KeyringPair
|
||||||
|
*/
|
||||||
|
setSigner (signer: Signer) {
|
||||||
|
this._rx.signer = signer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Derived results that are injected into the API, allowing for combinations of various query results.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* api.derive.chain.bestNumber((number) => {
|
||||||
|
* console.log('best number', number);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get derive (): Derive<CodecResult, SubscriptionResult> {
|
||||||
|
assert(!isUndefined(this._derive), INIT_ERROR);
|
||||||
|
|
||||||
|
return this._derive as Derive<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Contains all the chain state modules and their subsequent methods in the API. These are attached dynamically from the runtime metadata.
|
||||||
|
*
|
||||||
|
* All calls inside the namespace, is denoted by `section`.`method` and may take an optional query parameter. As an example, `api.query.timestamp.now()` (current block timestamp) does not take parameters, while `api.query.system.accountNonce(<accountId>)` (retrieving the associated nonce for an account), takes the `AccountId` as a parameter.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* api.query.balances.freeBalance(<accountId>, (balance) => {
|
||||||
|
* console.log('new balance', balance);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get query (): QueryableStorage<CodecResult, SubscriptionResult> {
|
||||||
|
assert(!isUndefined(this._query), INIT_ERROR);
|
||||||
|
|
||||||
|
return this._query as QueryableStorage<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Contains all the raw rpc sections and their subsequent methods in the API as defined by the jsonrpc interface definitions. Unlike the dynamic `api.query` and `api.tx` sections, these methods are fixed (although extensible with node upgrades) and not determined by the runtime.
|
||||||
|
*
|
||||||
|
* RPC endpoints available here allow for the query of chain, node and system information, in addition to providing interfaces for the raw queries of state (usine known keys) and the submission of transactions.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* api.rpc.chain.subscribeNewHead((header) => {
|
||||||
|
* console.log('new header', header);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get rpc (): DecoratedRpc<CodecResult, SubscriptionResult> {
|
||||||
|
return this._rpc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Contains all the extrinsic modules and their subsequent methods in the API. It allows for the construction of transactions and the submission thereof. These are attached dynamically from the runtime metadata.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* api.tx.balances
|
||||||
|
* .transfer(<recipientId>, <balance>)
|
||||||
|
* .signAndSend(<keyPair>, ({status}) => {
|
||||||
|
* console.log('tx status', status.asFinalized.toHex());
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get tx (): SubmittableExtrinsics<CodecResult, SubscriptionResult> {
|
||||||
|
assert(!isUndefined(this._extrinsics), INIT_ERROR);
|
||||||
|
|
||||||
|
return this._extrinsics as SubmittableExtrinsics<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Disconnect from the underlying provider, halting all comms
|
||||||
|
*/
|
||||||
|
disconnect (): void {
|
||||||
|
this._rpcBase.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Attach an eventemitter handler to listen to a specific event
|
||||||
|
*
|
||||||
|
* @param type The type of event to listen to. Available events are `connected`, `disconnected`, `ready` and `error`
|
||||||
|
* @param handler The callback to be called when the event fires. Depending on the event type, it could fire with additional arguments.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* api.on('connected', () => {
|
||||||
|
* console.log('API has been connected to the endpoint');
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* api.on('disconnected', () => {
|
||||||
|
* console.log('API has been disconnected from the endpoint');
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
on (type: ApiInterface$Events, handler: (...args: Array<any>) => any): this {
|
||||||
|
this._eventemitter.on(type, handler);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Attach an one-time eventemitter handler to listen to a specific event
|
||||||
|
*
|
||||||
|
* @param type The type of event to listen to. Available events are `connected`, `disconnected`, `ready` and `error`
|
||||||
|
* @param handler The callback to be called when the event fires. Depending on the event type, it could fire with additional arguments.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* api.once('connected', () => {
|
||||||
|
* console.log('API has been connected to the endpoint');
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* api.once('disconnected', () => {
|
||||||
|
* console.log('API has been disconnected from the endpoint');
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
once (type: ApiInterface$Events, handler: (...args: Array<any>) => any): this {
|
||||||
|
this._eventemitter.once(type, handler);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Register additional user-defined of chain-specific types in the type registry
|
||||||
|
*/
|
||||||
|
registerTypes (types?: RegistryTypes): void {
|
||||||
|
if (types) {
|
||||||
|
getTypeRegistry().register(types);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract onCall (method: OnCallFunction<RxResult, RxResult>, params?: Array<CodecArg>, callback?: CodecCallback, needsCallback?: boolean): CodecResult | SubscriptionResult;
|
||||||
|
|
||||||
|
private emit (type: ApiInterface$Events, ...args: Array<any>): void {
|
||||||
|
this._eventemitter.emit(type, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private init (): void {
|
||||||
|
let healthTimer: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
this._rpcBase._provider.on('disconnected', () => {
|
||||||
|
this.emit('disconnected');
|
||||||
|
|
||||||
|
if (healthTimer) {
|
||||||
|
clearInterval(healthTimer);
|
||||||
|
healthTimer = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._rpcBase._provider.on('error', (error) => {
|
||||||
|
this.emit('error', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._rpcBase._provider.on('connected', async () => {
|
||||||
|
this.emit('connected');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [hasMeta, cryptoReady] = await Promise.all([
|
||||||
|
this.loadMeta(),
|
||||||
|
cryptoWaitReady()
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (hasMeta && !this._isReady && cryptoReady) {
|
||||||
|
this._isReady = true;
|
||||||
|
|
||||||
|
this.emit('ready', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
healthTimer = setInterval(() => {
|
||||||
|
this._rpcRx.system.health().toPromise().catch(() => {
|
||||||
|
// ignore
|
||||||
|
});
|
||||||
|
}, KEEPALIVE_INTERVAL);
|
||||||
|
} catch (error) {
|
||||||
|
l.error('FATAL: Unable to initialize the API: ', error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadMeta (): Promise<boolean> {
|
||||||
|
// only load from on-chain if we are not a clone (default path), alternatively
|
||||||
|
// just use the values from the source instance provided
|
||||||
|
if (!this._options.source || !this._options.source._isReady) {
|
||||||
|
this._runtimeMetadata = await this._rpcBase.state.getMetadata();
|
||||||
|
this._runtimeVersion = await this._rpcBase.chain.getRuntimeVersion();
|
||||||
|
this._genesisHash = await this._rpcBase.chain.getBlockHash(0);
|
||||||
|
|
||||||
|
// get unique types & validate
|
||||||
|
this.runtimeMetadata.getUniqTypes(false);
|
||||||
|
} else {
|
||||||
|
this._runtimeMetadata = this._options.source.runtimeMetadata;
|
||||||
|
this._runtimeVersion = this._options.source.runtimeVersion;
|
||||||
|
this._genesisHash = this._options.source.genesisHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
const extrinsics = extrinsicsFromMeta(this.runtimeMetadata.asV0);
|
||||||
|
const storage = storageFromMeta(this.runtimeMetadata.asV0);
|
||||||
|
|
||||||
|
this._extrinsics = this.decorateExtrinsics(extrinsics, this.onCall);
|
||||||
|
this._query = this.decorateStorage(storage, this.onCall) as any; // FIXME 3.4.1
|
||||||
|
this._derive = this.decorateDerive(this._rx as ApiInterface$Rx, this.onCall) as any; // FIXME 3.4.1
|
||||||
|
|
||||||
|
this._rx.genesisHash = this._genesisHash;
|
||||||
|
this._rx.runtimeVersion = this._runtimeVersion;
|
||||||
|
this._rx.tx = this.decorateExtrinsics(extrinsics, rxOnCall);
|
||||||
|
this._rx.query = this.decorateStorage(storage, rxOnCall);
|
||||||
|
this._rx.derive = this.decorateDerive(this._rx as ApiInterface$Rx, rxOnCall);
|
||||||
|
|
||||||
|
// only inject if we are not a clone (global init)
|
||||||
|
if (!this._options.source) {
|
||||||
|
Event.injectMetadata(this.runtimeMetadata.asV0);
|
||||||
|
Method.injectMethods(extrinsics);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private decorateFunctionMeta (input: MetaDecoration, output: MetaDecoration): MetaDecoration {
|
||||||
|
output.meta = input.meta;
|
||||||
|
output.method = input.method;
|
||||||
|
output.section = input.section;
|
||||||
|
output.toJSON = input.toJSON;
|
||||||
|
|
||||||
|
if (input.callIndex) {
|
||||||
|
output.callIndex = input.callIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private decorateRpc<C, S> (rpc: RpcRx, onCall: OnCallDefinition<C, S>): DecoratedRpc<C, S> {
|
||||||
|
return ['author', 'chain', 'state', 'system'].reduce((result, _sectionName) => {
|
||||||
|
const sectionName = _sectionName as keyof DecoratedRpc<C, S>;
|
||||||
|
|
||||||
|
result[sectionName] = Object.keys(rpc[sectionName]).reduce((section, methodName) => {
|
||||||
|
// FIXME Find a better way to know if a particular method is a subscription or not
|
||||||
|
const needsCallback = methodName.includes('subscribe');
|
||||||
|
// These signatures are allowed and exposed here (bit or a stoopid way, but checked
|
||||||
|
// RPCs and we have 3 max args, with subs max one arg... YMMV) -
|
||||||
|
// (arg1?: CodecArg, arg2?: CodecArg, arg3?: CodecArg): CodecResult;
|
||||||
|
// (arg1: CodecArg, callback: CodecCallback): SubscriptionResult;
|
||||||
|
// (callback: CodecCallback): SubscriptionResult;
|
||||||
|
const method = ((...args: Array<any>): C | S => {
|
||||||
|
let callback: CodecCallback | undefined;
|
||||||
|
let params = args;
|
||||||
|
|
||||||
|
if (args.length && isFunction(args[args.length - 1])) {
|
||||||
|
callback = args[args.length - 1];
|
||||||
|
params = args.slice(0, args.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return onCall(rpc[sectionName][methodName], params, callback, needsCallback);
|
||||||
|
}) as DecoratedRpc$Method<C, S>;
|
||||||
|
|
||||||
|
section[methodName] = method;
|
||||||
|
|
||||||
|
return section;
|
||||||
|
}, {} as DecoratedRpc$Section<C, S>);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, {} as DecoratedRpc<C, S>);
|
||||||
|
}
|
||||||
|
|
||||||
|
private decorateExtrinsics<C, S> (extrinsics: ModulesWithMethods, onCall: OnCallDefinition<C, S>): SubmittableExtrinsics<C, S> {
|
||||||
|
return Object.keys(extrinsics).reduce((result, sectionName) => {
|
||||||
|
const section = extrinsics[sectionName];
|
||||||
|
|
||||||
|
result[sectionName] = Object.keys(section).reduce((result, methodName) => {
|
||||||
|
result[methodName] = this.decorateExtrinsicEntry(section[methodName], onCall);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, {} as SubmittableModuleExtrinsics<C, S>);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, {} as SubmittableExtrinsics<C, S>);
|
||||||
|
}
|
||||||
|
|
||||||
|
private decorateExtrinsicEntry<C, S> (method: MethodFunction, onCall: OnCallDefinition<C, S>): SubmittableExtrinsicFunction<C, S> {
|
||||||
|
const decorated: any = (...params: Array<CodecArg>): SubmittableExtrinsic<C, S> =>
|
||||||
|
createSubmittable(this.type, this._rx as ApiInterface$Rx, onCall, method(...params));
|
||||||
|
|
||||||
|
return this.decorateFunctionMeta(method, decorated) as SubmittableExtrinsicFunction<C, S>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private decorateStorage<C, S> (storage: Storage, onCall: OnCallDefinition<C, S>): QueryableStorage<C, S> {
|
||||||
|
return Object.keys(storage).reduce((result, sectionName) => {
|
||||||
|
const section = storage[sectionName];
|
||||||
|
|
||||||
|
result[sectionName] = Object.keys(section).reduce((result, methodName) => {
|
||||||
|
result[methodName] = this.decorateStorageEntry(section[methodName], onCall);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, {} as QueryableModuleStorage<C, S>);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, {} as QueryableStorage<C, S>);
|
||||||
|
}
|
||||||
|
|
||||||
|
private decorateStorageEntry<C, S> (method: StorageFunction, onCall: OnCallDefinition<C, S>): QueryableStorageFunction<C, S> {
|
||||||
|
// These signatures are allowed and exposed here -
|
||||||
|
// (arg?: CodecArg): CodecResult;
|
||||||
|
// (arg: CodecArg, callback: CodecCallback): SubscriptionResult;
|
||||||
|
// (callback: CodecCallback): SubscriptionResult;
|
||||||
|
const decorated = ((...args: Array<any>): C | S => {
|
||||||
|
let callback: CodecCallback | undefined;
|
||||||
|
let params = args;
|
||||||
|
|
||||||
|
if (args.length && isFunction(args[args.length - 1])) {
|
||||||
|
callback = args[args.length - 1];
|
||||||
|
params = args.slice(0, args.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.headKey && params.length === 0) {
|
||||||
|
return this.decorateStorageEntryLinked(method, onCall, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return onCall(
|
||||||
|
(arg: CodecArg) => this._rpcRx.state
|
||||||
|
.subscribeStorage([[method, arg]])
|
||||||
|
.pipe(
|
||||||
|
// state_storage returns an array of values, since we have just subscribed to
|
||||||
|
// a single entry, we pull that from the array and return it as-is
|
||||||
|
map((result: Array<Codec>): Codec =>
|
||||||
|
result[0]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
params,
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
}) as QueryableStorageFunction<C, S>;
|
||||||
|
|
||||||
|
decorated.at = (hash: Hash | Uint8Array | string, arg?: CodecArg): C =>
|
||||||
|
onCall(
|
||||||
|
(arg: CodecArg) => this._rpcRx.state.getStorage([method, arg], hash),
|
||||||
|
[arg]
|
||||||
|
) as C;
|
||||||
|
|
||||||
|
// FIXME The unknown cast is needed since the onCall result, `C | S` cannot
|
||||||
|
// be converted from C to the actual result required
|
||||||
|
decorated.hash = (arg?: CodecArg): HashResult<C, S> =>
|
||||||
|
onCall(
|
||||||
|
(arg: CodecArg) => this._rpcRx.state.getStorageHash([method, arg]),
|
||||||
|
[arg]
|
||||||
|
) as unknown as HashResult<C, S>;
|
||||||
|
|
||||||
|
// FIXME as above...
|
||||||
|
decorated.size = (arg?: CodecArg): U64Result<C, S> =>
|
||||||
|
onCall(
|
||||||
|
(arg: CodecArg) => this._rpcRx.state.getStorageSize([method, arg]),
|
||||||
|
[arg]
|
||||||
|
) as unknown as U64Result<C, S>;
|
||||||
|
|
||||||
|
decorated.key = (arg?: CodecArg): string =>
|
||||||
|
u8aToHex(compactStripLength(method(arg))[1]);
|
||||||
|
|
||||||
|
return this.decorateFunctionMeta(method, decorated) as QueryableStorageFunction<C, S>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private decorateStorageEntryLinked<C, S> (method: StorageFunction, onCall: OnCallDefinition<C, S>, callback: CodecCallback | undefined): C | S {
|
||||||
|
const result: Map<Codec, [Codec, Linkage<Codec>]> = new Map();
|
||||||
|
let subject: BehaviorSubject<LinkageResult>;
|
||||||
|
let head: Codec | null = null;
|
||||||
|
|
||||||
|
// retrieve a value based on the key, iterating if it has a next entry. Since
|
||||||
|
// entries can be re-linked in the middle of a list, we subscribe here to make
|
||||||
|
// sure we catch any updates, no matter the list position
|
||||||
|
const getNext = (key: Codec): Observable<LinkageResult> => {
|
||||||
|
return this._rpcRx.state.subscribeStorage([[method, key]])
|
||||||
|
.pipe(
|
||||||
|
switchMap(([data]: [[Codec, Linkage<Codec>]]) => {
|
||||||
|
const linkage = data[1];
|
||||||
|
|
||||||
|
result.set(key, data);
|
||||||
|
|
||||||
|
// iterate from this key to the children, constructing
|
||||||
|
// entries for all those found and available
|
||||||
|
if (linkage.next.isSome) {
|
||||||
|
return getNext(linkage.next.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = [];
|
||||||
|
const values = [];
|
||||||
|
let nextKey = head;
|
||||||
|
|
||||||
|
// loop through the results collected, starting at the head an re-creating
|
||||||
|
// the list. Our map may have old entries, based on the linking these will
|
||||||
|
// not be returned in the final result
|
||||||
|
while (nextKey) {
|
||||||
|
const entry = result.get(nextKey);
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [item, linkage] = entry;
|
||||||
|
|
||||||
|
keys.push(nextKey);
|
||||||
|
values.push(item);
|
||||||
|
|
||||||
|
nextKey = linkage.next && linkage.next.unwrapOr(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextResult = values.length
|
||||||
|
? new LinkageResult(
|
||||||
|
[keys[0].constructor as any, keys],
|
||||||
|
[values[0].constructor as any, values]
|
||||||
|
)
|
||||||
|
: new LinkageResult(
|
||||||
|
[Null, []],
|
||||||
|
[Null, []]
|
||||||
|
);
|
||||||
|
|
||||||
|
// we set our result into a subject so we have a single observable for
|
||||||
|
// which the value changes over time. Initially create, follow-up next
|
||||||
|
if (subject) {
|
||||||
|
subject.next(nextResult);
|
||||||
|
} else {
|
||||||
|
subject = new BehaviorSubject(nextResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return subject;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// this handles the case where the head changes effectively, i.e. a new entry
|
||||||
|
// appears at the top of the list, the new getNext gets kicked off
|
||||||
|
return onCall(
|
||||||
|
(arg: CodecArg) => this._rpcRx.state
|
||||||
|
.subscribeStorage([arg])
|
||||||
|
.pipe(
|
||||||
|
switchMap(([key]: Array<Codec>) => {
|
||||||
|
head = key;
|
||||||
|
|
||||||
|
return getNext(key);
|
||||||
|
})
|
||||||
|
),
|
||||||
|
[method.headKey],
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private decorateDerive<C, S> (apiRx: ApiInterface$Rx, onCall: OnCallDefinition<C, S>): Derive<C, S> {
|
||||||
|
const derive = decorateDerive(apiRx, this._options.derives);
|
||||||
|
|
||||||
|
return Object.keys(derive).reduce((result, _sectionName) => {
|
||||||
|
const sectionName = _sectionName as keyof DeriveInterface;
|
||||||
|
|
||||||
|
result[sectionName] = Object.keys(derive[sectionName]).reduce((section, methodName) => {
|
||||||
|
// FIXME The callback extraction we do way too much - de-dupe
|
||||||
|
const method = (...args: Array<any>): C | S => {
|
||||||
|
let callback: CodecCallback | undefined;
|
||||||
|
let params = args;
|
||||||
|
|
||||||
|
if (args.length && isFunction(args[args.length - 1])) {
|
||||||
|
callback = args[args.length - 1];
|
||||||
|
params = args.slice(0, args.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return onCall((derive[sectionName] as any)[methodName], params, callback, !!callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
section[methodName] = method as any; // CodecCallback or CodecArg form
|
||||||
|
|
||||||
|
return section;
|
||||||
|
}, {} as DeriveSection<C, S>);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, {} as Derive<C, S>);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { KeyringPair } from '@plugnet/keyring/types';
|
||||||
|
import { AccountId, Address, ExtrinsicStatus, EventRecord, getTypeRegistry, Hash, Index, Method, SignedBlock, Struct, Vector } from '@plugnet/types';
|
||||||
|
import { Codec, CodecCallback, IExtrinsic, SignatureOptions } from '@plugnet/types/types';
|
||||||
|
import { ApiInterface$Rx, ApiType, OnCallDefinition, Signer } from './types';
|
||||||
|
|
||||||
|
import { Observable, of, combineLatest } from 'rxjs';
|
||||||
|
import { first, map, mergeMap, switchMap, tap } from 'rxjs/operators';
|
||||||
|
import { assert, isBn, isFunction, isNumber, isUndefined } from '@plugnet/util';
|
||||||
|
|
||||||
|
import filterEvents from './util/filterEvents';
|
||||||
|
|
||||||
|
type StatusCb = (result: SubmittableResult) => any;
|
||||||
|
|
||||||
|
type SumbitableResultResult<CodecResult, SubscriptionResult> =
|
||||||
|
CodecResult extends Observable<any>
|
||||||
|
? Observable<SubmittableResult>
|
||||||
|
: Promise<Hash>;
|
||||||
|
|
||||||
|
type SumbitableResultSubscription<CodecResult, SubscriptionResult> =
|
||||||
|
SubscriptionResult extends Observable<any>
|
||||||
|
? Observable<SubmittableResult>
|
||||||
|
: Promise<() => void>;
|
||||||
|
|
||||||
|
export class SubmittableResult extends Struct {
|
||||||
|
constructor (value?: any) {
|
||||||
|
super({
|
||||||
|
events: Vector.with(EventRecord),
|
||||||
|
status: ExtrinsicStatus
|
||||||
|
}, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description the contained events
|
||||||
|
*/
|
||||||
|
get events (): Array<EventRecord> {
|
||||||
|
return this.get('events') as Vector<EventRecord>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description the status
|
||||||
|
*/
|
||||||
|
get status (): ExtrinsicStatus {
|
||||||
|
return this.get('status') as ExtrinsicStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Finds an EventRecord for the specified method & section
|
||||||
|
*/
|
||||||
|
findRecord (section: string, method: string): EventRecord | undefined {
|
||||||
|
return this.events.find(({ event }) =>
|
||||||
|
event.section === section && event.method === method
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubmittableExtrinsic<CodecResult, SubscriptionResult> extends IExtrinsic {
|
||||||
|
send (): SumbitableResultResult<CodecResult, SubscriptionResult>;
|
||||||
|
|
||||||
|
send (statusCb: (result: SubmittableResult) => any): SumbitableResultSubscription<CodecResult, SubscriptionResult>;
|
||||||
|
|
||||||
|
sign (account: KeyringPair, _options: Partial<SignatureOptions>): this;
|
||||||
|
|
||||||
|
signAndSend (account: KeyringPair | string | AccountId | Address, options?: Partial<Partial<SignatureOptions>>): SumbitableResultResult<CodecResult, SubscriptionResult>;
|
||||||
|
|
||||||
|
signAndSend (account: KeyringPair | string | AccountId | Address, statusCb: StatusCb): SumbitableResultSubscription<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createSubmittableExtrinsic<CodecResult, SubscriptionResult> (type: ApiType, api: ApiInterface$Rx, onCall: OnCallDefinition<CodecResult, SubscriptionResult>, extrinsic: Method, trackingCb?: (result: SubmittableResult) => any): SubmittableExtrinsic<CodecResult, SubscriptionResult> {
|
||||||
|
const _extrinsic = new (getTypeRegistry().getOrThrow('Extrinsic'))(extrinsic) as SubmittableExtrinsic<CodecResult, SubscriptionResult>;
|
||||||
|
const _noStatusCb = type === 'rxjs';
|
||||||
|
|
||||||
|
function updateSigner (updateId: number, status: Hash | SubmittableResult): void {
|
||||||
|
if ((updateId !== -1) && api.signer && api.signer.update) {
|
||||||
|
api.signer.update(updateId, status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusObservable (status: ExtrinsicStatus): Observable<SubmittableResult> {
|
||||||
|
if (!status.isFinalized) {
|
||||||
|
const result = new SubmittableResult({ status });
|
||||||
|
|
||||||
|
trackingCb && trackingCb(result);
|
||||||
|
|
||||||
|
return of(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockHash = status.asFinalized;
|
||||||
|
|
||||||
|
return combineLatest(
|
||||||
|
api.rpc.chain.getBlock(blockHash) as Observable<SignedBlock>,
|
||||||
|
api.query.system.events.at(blockHash) as Observable<Vector<EventRecord>>
|
||||||
|
).pipe(
|
||||||
|
map(([signedBlock, allEvents]) => {
|
||||||
|
const result = new SubmittableResult({
|
||||||
|
events: filterEvents(_extrinsic.hash, signedBlock, allEvents),
|
||||||
|
status
|
||||||
|
});
|
||||||
|
|
||||||
|
trackingCb && trackingCb(result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendObservable (updateId: number = -1): Observable<Hash> {
|
||||||
|
return (api.rpc.author
|
||||||
|
.submitExtrinsic(_extrinsic) as Observable<Hash>)
|
||||||
|
.pipe(
|
||||||
|
tap((hash) => {
|
||||||
|
updateSigner(updateId, hash);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribeObservable (updateId: number = -1): Observable<SubmittableResult> {
|
||||||
|
return (api.rpc.author
|
||||||
|
.submitAndWatchExtrinsic(_extrinsic) as Observable<ExtrinsicStatus>)
|
||||||
|
.pipe(
|
||||||
|
switchMap((status) =>
|
||||||
|
statusObservable(status)
|
||||||
|
),
|
||||||
|
tap((status) => {
|
||||||
|
updateSigner(updateId, status);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandOptions (options: Partial<SignatureOptions>): SignatureOptions {
|
||||||
|
return {
|
||||||
|
blockHash: api.genesisHash,
|
||||||
|
version: api.runtimeVersion,
|
||||||
|
...options
|
||||||
|
} as SignatureOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const signOrigin = _extrinsic.sign;
|
||||||
|
Object.defineProperties(
|
||||||
|
_extrinsic,
|
||||||
|
{
|
||||||
|
send: {
|
||||||
|
value: function (statusCb?: (result: SubmittableResult) => any): SumbitableResultResult<CodecResult, SubscriptionResult> | SumbitableResultSubscription<CodecResult, SubscriptionResult> {
|
||||||
|
const isSubscription = _noStatusCb || !!statusCb;
|
||||||
|
|
||||||
|
return onCall(
|
||||||
|
() => isSubscription
|
||||||
|
? subscribeObservable()
|
||||||
|
: sendObservable(),
|
||||||
|
[],
|
||||||
|
statusCb as CodecCallback,
|
||||||
|
isSubscription
|
||||||
|
) as unknown as SumbitableResultSubscription<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sign: {
|
||||||
|
value: function (account: KeyringPair, _options: Partial<SignatureOptions>): SubmittableExtrinsic<CodecResult, SubscriptionResult> {
|
||||||
|
// HACK here we actually override nonce if it was specified (backwards compat for
|
||||||
|
// the previous signature - don't let userspace break, but allow then time to upgrade)
|
||||||
|
const options: Partial<SignatureOptions> = isBn(_options) || isNumber(_options)
|
||||||
|
? { nonce: _options as any as number }
|
||||||
|
: _options;
|
||||||
|
|
||||||
|
signOrigin.apply(_extrinsic, [account, expandOptions(options)]);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
signAndSend: {
|
||||||
|
value: function (account: KeyringPair | string | AccountId | Address, _options?: Partial<Partial<SignatureOptions>> | StatusCb, statusCb?: StatusCb): SumbitableResultResult<CodecResult, SubscriptionResult> | SumbitableResultSubscription<CodecResult, SubscriptionResult> {
|
||||||
|
let options: Partial<Partial<SignatureOptions>> = {};
|
||||||
|
|
||||||
|
if (isFunction(_options)) {
|
||||||
|
statusCb = _options;
|
||||||
|
} else {
|
||||||
|
options = _options || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSubscription = _noStatusCb || !!statusCb;
|
||||||
|
const isKeyringPair = isFunction((account as KeyringPair).address) && isFunction((account as KeyringPair).sign);
|
||||||
|
const address = isKeyringPair ? (account as KeyringPair).address() : account.toString();
|
||||||
|
let updateId: number | undefined;
|
||||||
|
|
||||||
|
return onCall(
|
||||||
|
() => ((
|
||||||
|
isUndefined(options.nonce)
|
||||||
|
? api.query.system.accountNonce(address) as Observable<Index>
|
||||||
|
: of(new Index(options.nonce))
|
||||||
|
).pipe(
|
||||||
|
first(),
|
||||||
|
mergeMap(async (nonce) => {
|
||||||
|
if (isKeyringPair) {
|
||||||
|
this.sign(account as KeyringPair, { ...options, nonce });
|
||||||
|
} else {
|
||||||
|
assert(api.signer, 'no signer exists');
|
||||||
|
|
||||||
|
updateId = await (api.signer as Signer).sign(_extrinsic, address, expandOptions({
|
||||||
|
...options,
|
||||||
|
nonce
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
switchMap(() => {
|
||||||
|
return isSubscription
|
||||||
|
? subscribeObservable(updateId)
|
||||||
|
: sendObservable(updateId) as any; // ???
|
||||||
|
})
|
||||||
|
) as Observable<Codec>),
|
||||||
|
[],
|
||||||
|
statusCb as CodecCallback,
|
||||||
|
isSubscription
|
||||||
|
) as unknown as SumbitableResultSubscription<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return _extrinsic;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
// Simple non-runnable checks to test type definitions in the editor itself
|
||||||
|
|
||||||
|
import { ApiPromise } from '@plugnet/api';
|
||||||
|
import testKeyring from '@plugnet/keyring/testingPairs';
|
||||||
|
import { IExtrinsic, IMethod } from '@plugnet/types/types';
|
||||||
|
import { Header, HeaderExtended } from '@plugnet/types';
|
||||||
|
|
||||||
|
export default async function test () {
|
||||||
|
const api = await ApiPromise.create();
|
||||||
|
const keyring = testKeyring();
|
||||||
|
|
||||||
|
const intentions = await api.query.staking.intentions();
|
||||||
|
console.log('intentions:', intentions);
|
||||||
|
|
||||||
|
await api.query.staking.intentions((intentions) => {
|
||||||
|
console.log('intentions:', intentions);
|
||||||
|
});
|
||||||
|
|
||||||
|
await api.rpc.chain.subscribeNewHead((header: Header) => {
|
||||||
|
console.log('current blockNumber:', header.blockNumber);
|
||||||
|
});
|
||||||
|
|
||||||
|
await api.derive.chain.subscribeNewHead((header: HeaderExtended) => {
|
||||||
|
console.log('current author:', header.author);
|
||||||
|
});
|
||||||
|
|
||||||
|
const transfer = api.tx.balances.transfer(keyring.bob.address(), 12345);
|
||||||
|
|
||||||
|
console.log('transfer as Method', transfer as IMethod);
|
||||||
|
console.log('transfer as Extrinsic', transfer as IExtrinsic);
|
||||||
|
|
||||||
|
const hash = await transfer.signAndSend(keyring.alice);
|
||||||
|
console.log('hash:', hash.toHex());
|
||||||
|
|
||||||
|
const unsub = await api.tx.balances
|
||||||
|
.transfer(keyring.bob.address(), 12345)
|
||||||
|
.signAndSend(keyring.alice, ({ status }) => {
|
||||||
|
console.log('transfer status:', status.type);
|
||||||
|
|
||||||
|
unsub();
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
export { WsProvider } from '@plugnet/rpc-provider';
|
||||||
|
import { assertSingletonPackage } from '@plugnet/util';
|
||||||
|
|
||||||
|
assertSingletonPackage('@polkadot/api');
|
||||||
|
|
||||||
|
export { default as ApiPromise } from './promise';
|
||||||
|
export { default as ApiRx } from './rx';
|
||||||
|
export { default as SubmittableExtrinsic, SubmittableResult } from './SubmittableExtrinsic';
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { ProviderInterface } from '@plugnet/rpc-provider/types';
|
||||||
|
import { CodecArg, CodecCallback } from '@plugnet/types/types';
|
||||||
|
import { RxResult } from '../rx/types';
|
||||||
|
import { ApiOptions, OnCallFunction } from '../types';
|
||||||
|
import { ApiPromiseInterface, CodecResult, SubscriptionResult } from './types';
|
||||||
|
|
||||||
|
import { EMPTY } from 'rxjs';
|
||||||
|
import { catchError, first, tap } from 'rxjs/operators';
|
||||||
|
import { isFunction, assert } from '@plugnet/util';
|
||||||
|
|
||||||
|
import ApiBase from '../Base';
|
||||||
|
import Combinator, { CombinatorCallback, CombinatorFunction } from './Combinator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* # @polkadot/api/promise
|
||||||
|
*
|
||||||
|
* ## Overview
|
||||||
|
*
|
||||||
|
* @name ApiPromise
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* ApiPromise is a standard JavaScript wrapper around the RPC and interfaces on the Polkadot network. As a full Promise-based, all interface calls return Promises, including the static `.create(...)`. Subscription calls utilise `(value) => {}` callbacks to pass through the latest values.
|
||||||
|
*
|
||||||
|
* The API is well suited to real-time applications where either the single-shot state is needed or use is to be made of the subscription-based features of Polkadot (and Substrate) clients.
|
||||||
|
*
|
||||||
|
* @see [[ApiRx]]
|
||||||
|
*
|
||||||
|
* ## Usage
|
||||||
|
*
|
||||||
|
* Making rpc calls -
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* import ApiPromise from '@plugnet/api/promise';
|
||||||
|
*
|
||||||
|
* // initialise via static create
|
||||||
|
* const api = await ApiPromise.create();
|
||||||
|
*
|
||||||
|
* // make a subscription to the network head
|
||||||
|
* api.rpc.chain.subscribeNewHead((header) => {
|
||||||
|
* console.log(`Chain is at #${header.blockNumber}`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* Subscribing to chain state -
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* import { ApiPromise, WsProvider } from '@plugnet/api';
|
||||||
|
*
|
||||||
|
* // initialise a provider with a specific endpoint
|
||||||
|
* const provider = new WsProvider('wss://example.com:9944')
|
||||||
|
*
|
||||||
|
* // initialise via isReady & new with specific provider
|
||||||
|
* const api = await new ApiPromise(provider).isReady;
|
||||||
|
*
|
||||||
|
* // retrieve the block target time
|
||||||
|
* const blockPeriod = await api.query.timestamp.blockPeriod().toNumber();
|
||||||
|
* let last = 0;
|
||||||
|
*
|
||||||
|
* // subscribe to the current block timestamp, updates automatically (callback provided)
|
||||||
|
* api.query.timestamp.now((timestamp) => {
|
||||||
|
* const elapsed = last
|
||||||
|
* ? `, ${timestamp.toNumber() - last}s since last`
|
||||||
|
* : '';
|
||||||
|
*
|
||||||
|
* last = timestamp.toNumber();
|
||||||
|
* console.log(`timestamp ${timestamp}${elapsed} (${blockPeriod}s target)`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* Submitting a transaction -
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* import ApiPromise from '@plugnet/api/promise';
|
||||||
|
*
|
||||||
|
* ApiPromise.create().then((api) => {
|
||||||
|
* const nonce = await api.query.system.accountNonce(keyring.alice.address());
|
||||||
|
*
|
||||||
|
* api.tx.balances
|
||||||
|
* // create transfer
|
||||||
|
* transfer(keyring.bob.address(), 12345)
|
||||||
|
* // sign the transcation
|
||||||
|
* .sign(keyring.alice, { nonce })
|
||||||
|
* // send the transaction (optional status callback)
|
||||||
|
* .send((status) => {
|
||||||
|
* console.log(`current status ${status.type}`);
|
||||||
|
* })
|
||||||
|
* // retrieve the submitted extrinsic hash
|
||||||
|
* .then((hash) => {
|
||||||
|
* console.log(`submitted with hash ${hash}`);
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export default class ApiPromise extends ApiBase<CodecResult, SubscriptionResult> implements ApiPromiseInterface {
|
||||||
|
private _isReadyPromise: Promise<ApiPromise>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Creates an ApiPromise instance using the supplied provider. Returns an Promise containing the actual Api instance.
|
||||||
|
*
|
||||||
|
* @param options options that is passed to the class contructor. Can be either [[ApiOptions]] or a
|
||||||
|
* provider (see the constructor arguments)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* import Api from '@plugnet/api/promise';
|
||||||
|
*
|
||||||
|
* Api.create().then(async (api) => {
|
||||||
|
* const timestamp = await api.query.timestamp.now();
|
||||||
|
*
|
||||||
|
* console.log(`lastest block timestamp ${timestamp}`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
static create (options: ApiOptions | ProviderInterface = {}): Promise<ApiPromise> {
|
||||||
|
return new ApiPromise(options).isReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Creates an instance of the ApiPromise class
|
||||||
|
*
|
||||||
|
* @param options Options to create an instance. This can be either [[ApiOptions]] or
|
||||||
|
* an [[WsProvider]].
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* import Api from '@plugnet/api/promise';
|
||||||
|
*
|
||||||
|
* new Api().isReady.then((api) => {
|
||||||
|
* api.rpc.subscribeNewHead((header) => {
|
||||||
|
* console.log(`new block #${header.blockNumber.toNumber()}`);
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
constructor (options?: ApiOptions | ProviderInterface) {
|
||||||
|
super(options, 'promise');
|
||||||
|
|
||||||
|
this._isReadyPromise = new Promise((resolveReady) =>
|
||||||
|
super.once('ready', () =>
|
||||||
|
resolveReady(this)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Promise that returns the first time we are connected and loaded
|
||||||
|
*/
|
||||||
|
get isReady (): Promise<ApiPromise> {
|
||||||
|
return this._isReadyPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Returns a clone of this ApiPromise instance (new underlying provider connection)
|
||||||
|
*/
|
||||||
|
clone (): ApiPromise {
|
||||||
|
return new ApiPromise({
|
||||||
|
...this._options,
|
||||||
|
source: this
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Creates a combinator that can be used to combine the latest results from multiple subscriptions
|
||||||
|
* @param fns An array of function to combine, each in the form of `(cb: (value: void)) => void`
|
||||||
|
* @param callback A callback that will return an Array of all the values this combinator has been applied to
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* const address = '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFacT7';
|
||||||
|
*
|
||||||
|
* // combines values from balance & nonce as it updates
|
||||||
|
* api.combineLatest([
|
||||||
|
* api.rpc.chain.subscribeNewHead,
|
||||||
|
* [api.query.balances.freeBalance, address],
|
||||||
|
* (cb) => api.query.system.accountNonce(address, cb)
|
||||||
|
* ], ([head, balance, nonce]) => {
|
||||||
|
* console.log(`#${head.number}: You have ${balance} units, with ${nonce} transactions sent`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
async combineLatest (fns: Array<CombinatorFunction | [CombinatorFunction, ...Array<any>]>, callback: CombinatorCallback): SubscriptionResult {
|
||||||
|
const combinator = new Combinator(fns, callback);
|
||||||
|
|
||||||
|
return (): void => {
|
||||||
|
combinator.unsubscribe();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onCall (method: OnCallFunction<RxResult, RxResult>, params: Array<CodecArg> = [], callback?: CodecCallback, needsCallback?: boolean): CodecResult | SubscriptionResult {
|
||||||
|
// When we need a subscription, ensure that a valid callback is actually passed
|
||||||
|
assert(!needsCallback || isFunction(callback), 'Expected a callback to be passed with subscriptions');
|
||||||
|
|
||||||
|
if (!callback) {
|
||||||
|
return method(...params).pipe(first()).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME TSLint shouts that type assertion is unnecessary, but tsc shouts
|
||||||
|
// when I remove it...
|
||||||
|
// tslint:disable-next-line
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let isCompleted = false;
|
||||||
|
const subscription = method(...params)
|
||||||
|
.pipe(
|
||||||
|
// if we find an error (invalid params, etc), reject the promise
|
||||||
|
catchError((error) => {
|
||||||
|
if (!isCompleted) {
|
||||||
|
isCompleted = true;
|
||||||
|
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we don't want to continue, so empty observable it is
|
||||||
|
return EMPTY;
|
||||||
|
}),
|
||||||
|
// upon the first result, resolve the with the unsub function
|
||||||
|
tap(() => {
|
||||||
|
if (!isCompleted) {
|
||||||
|
isCompleted = true;
|
||||||
|
|
||||||
|
resolve(() => subscription.unsubscribe());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe(callback);
|
||||||
|
}) as SubscriptionResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { SubscriptionResult } from './types';
|
||||||
|
|
||||||
|
import { isFunction } from '@plugnet/util';
|
||||||
|
|
||||||
|
export type CombinatorCallback = (value: Array<any>) => any;
|
||||||
|
export type CombinatorFunction = {
|
||||||
|
(cb: (value: any) => any): SubscriptionResult
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Combinator {
|
||||||
|
protected _allHasFired: boolean = false;
|
||||||
|
protected _callback: CombinatorCallback;
|
||||||
|
protected _fired: Array<boolean> = [];
|
||||||
|
protected _fns: Array<CombinatorFunction> = [];
|
||||||
|
protected _isActive: boolean = true;
|
||||||
|
protected _results: Array<any> = [];
|
||||||
|
protected _subscriptions: Array<SubscriptionResult> = [];
|
||||||
|
|
||||||
|
constructor (fns: Array<CombinatorFunction | [CombinatorFunction, ...Array<any>]>, callback: CombinatorCallback) {
|
||||||
|
this._callback = callback;
|
||||||
|
this._subscriptions = fns.map(async (input, index) => {
|
||||||
|
const [fn, ...args] = Array.isArray(input)
|
||||||
|
? input
|
||||||
|
: [input];
|
||||||
|
|
||||||
|
this._fired.push(false);
|
||||||
|
this._fns.push(fn);
|
||||||
|
|
||||||
|
// @ts-ignore Not quite 100% how to have a variable number at the front here
|
||||||
|
return fn(...args, this.createCallback(index));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected allHasFired (): boolean {
|
||||||
|
if (!this._allHasFired) {
|
||||||
|
this._allHasFired = this._fired.filter((hasFired) => !hasFired).length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._allHasFired;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createCallback (index: number) {
|
||||||
|
return (value: any): void => {
|
||||||
|
this._fired[index] = true;
|
||||||
|
this._results[index] = value;
|
||||||
|
|
||||||
|
this.triggerUpdate();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected triggerUpdate (): void {
|
||||||
|
if (!this._isActive || !isFunction(this._callback) || !this.allHasFired()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._callback(this._results);
|
||||||
|
} catch (error) {
|
||||||
|
// swallow, we don't want the handler to trip us up
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe (): void {
|
||||||
|
if (!this._isActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._isActive = false;
|
||||||
|
|
||||||
|
this._subscriptions.forEach(async (subscription) => {
|
||||||
|
try {
|
||||||
|
const unsubscribe = await subscription;
|
||||||
|
|
||||||
|
if (isFunction(unsubscribe)) {
|
||||||
|
unsubscribe();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { SubscriptionResult } from './types';
|
||||||
|
|
||||||
|
import Combinator from './Combinator';
|
||||||
|
|
||||||
|
describe('Combinator', () => {
|
||||||
|
let fns: Array<(value: any) => void> = [];
|
||||||
|
const storeFn = async (cb: (value: any) => void): SubscriptionResult => {
|
||||||
|
fns.push(cb);
|
||||||
|
|
||||||
|
return () => undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fns = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('it triggers on all values', (done) => {
|
||||||
|
let count = 0;
|
||||||
|
const combinator = new Combinator(
|
||||||
|
[storeFn],
|
||||||
|
(value: Array<any>) => {
|
||||||
|
expect(value[0]).toEqual(`test${count}`);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count === 3) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fns[0]('test0');
|
||||||
|
fns[0]('test1');
|
||||||
|
fns[0]('test2');
|
||||||
|
|
||||||
|
expect(combinator).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('combines values from 2 sources, firing when it has all results', (done) => {
|
||||||
|
const combinator = new Combinator(
|
||||||
|
[storeFn, storeFn],
|
||||||
|
(value: Array<any>) => {
|
||||||
|
expect(value).toEqual(['test0', 'test1']);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fns[0]('test0');
|
||||||
|
fns[1]('test1');
|
||||||
|
|
||||||
|
expect(combinator).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('combines values from 2 sources, allowing multiple updates', (done) => {
|
||||||
|
let count = 0;
|
||||||
|
const combinator = new Combinator(
|
||||||
|
[storeFn, storeFn],
|
||||||
|
(value: Array<any>) => {
|
||||||
|
expect(value).toEqual(
|
||||||
|
count === 0
|
||||||
|
? ['test0', 'test1']
|
||||||
|
: ['test2', 'test1']);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (count === 2) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fns[0]('test0');
|
||||||
|
fns[1]('test1');
|
||||||
|
fns[0]('test2');
|
||||||
|
|
||||||
|
expect(combinator).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unsubscribes as required', (done) => {
|
||||||
|
const mocker = async () => done;
|
||||||
|
const combinator = new Combinator([
|
||||||
|
mocker,
|
||||||
|
async () => () => void 0
|
||||||
|
], (value: Array<any>) => {
|
||||||
|
// ignore
|
||||||
|
});
|
||||||
|
|
||||||
|
combinator.unsubscribe();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { default as ApiPromise } from './Api';
|
||||||
|
|
||||||
|
export default ApiPromise;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Codec } from '@plugnet/types/types';
|
||||||
|
import { QueryableStorageFunction as QueryableStorageFunctionBase, SubmittableExtrinsicFunction as SubmittableExtrinsicFunctionBase } from '../types';
|
||||||
|
|
||||||
|
import ApiBase from '../Base';
|
||||||
|
import { SubmittableExtrinsic as SubmittableExtrinsicBase } from '../SubmittableExtrinsic';
|
||||||
|
|
||||||
|
export type SubscriptionResult = Promise<() => any>;
|
||||||
|
|
||||||
|
export type CodecResult = Promise<Codec>;
|
||||||
|
|
||||||
|
export interface ApiPromiseInterface extends ApiBase<CodecResult, SubscriptionResult> {
|
||||||
|
readonly isReady: Promise<ApiPromiseInterface>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QueryableStorageFunction = QueryableStorageFunctionBase<CodecResult, SubscriptionResult>;
|
||||||
|
export type SubmittableExtrinsic = SubmittableExtrinsicBase<CodecResult, SubscriptionResult>;
|
||||||
|
export type SubmittableExtrinsicFunction = SubmittableExtrinsicFunctionBase<CodecResult, SubscriptionResult>;
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { ProviderInterface } from '@plugnet/rpc-provider/types';
|
||||||
|
import { CodecArg } from '@plugnet/types/types';
|
||||||
|
import { ApiRxInterface, RxResult } from './types';
|
||||||
|
import { ApiOptions, OnCallFunction } from '../types';
|
||||||
|
|
||||||
|
import { Observable, from } from 'rxjs';
|
||||||
|
|
||||||
|
import ApiBase from '../Base';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* # @polkadot/api/rx
|
||||||
|
*
|
||||||
|
* ## Overview
|
||||||
|
*
|
||||||
|
* @name ApiRx
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* ApiRx is a powerfull RxJS Observable wrapper around the RPC and interfaces on the Polkadot network. As a full Observable API, all interface calls return RxJS Observables, including the static `.create(...)`. In the same fashion and subscription-based methods return long-running Observables that update with the latest values.
|
||||||
|
*
|
||||||
|
* The API is well suited to real-time applications where the latest state is needed, unlocking the subscription-based features of Polkadot (and Substrate) clients. Some familiarity with RxJS is a requirement to use the API, however just understanding `.subscribe` and `.pipe` on Observables will unlock full-scale use thereof.
|
||||||
|
*
|
||||||
|
* @see [[ApiPromise]]
|
||||||
|
*
|
||||||
|
* ## Usage
|
||||||
|
*
|
||||||
|
* Making rpc calls -
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* import ApiRx from '@plugnet/api/rx';
|
||||||
|
*
|
||||||
|
* // initialise via Promise & static create
|
||||||
|
* const api = await ApiRx.create().toPromise();
|
||||||
|
*
|
||||||
|
* // make a call to retrieve the current network head
|
||||||
|
* api.rpc.chain.subscribeNewHead().subscribe((header) => {
|
||||||
|
* console.log(`Chain is at #${header.blockNumber}`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* Subscribing to chain state -
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* import { combineLatest } from 'rxjs';
|
||||||
|
* import { pairwise, switchMap } from 'rxjs/operators';
|
||||||
|
* import { ApiRx, WsProvider } from '@plugnet/api';
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* // initialise a provider with a specific endpoint
|
||||||
|
* const provider = new WsProvider('wss://example.com:9944')
|
||||||
|
*
|
||||||
|
* // initialise via isReady & new with specific provider
|
||||||
|
* new ApiRx(provider)
|
||||||
|
* .isReady
|
||||||
|
* .pipe(
|
||||||
|
* switchMap((api) =>
|
||||||
|
* combineLatest([
|
||||||
|
* api.query.timestamp.blockPeriod(),
|
||||||
|
* api.query.timestamp.now().pipe(pairwise())
|
||||||
|
* ])
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* .subscribe(([blockPeriod, timestamp]) => {
|
||||||
|
* const elapsed = timestamp[1].toNumber() - timestamp[0].toNumber();
|
||||||
|
* console.log(`timestamp ${timestamp[1]} \nelapsed ${elapsed} \n(${blockPeriod}s target)`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* Submitting a transaction -
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* import { first, switchMap } from 'rxjs/operators';
|
||||||
|
* import ApiRx from '@plugnet/api/rx';
|
||||||
|
*
|
||||||
|
* // import the test keyring (already has dev keys for Alice, Bob, Charlie, Eve & Ferdie)
|
||||||
|
* import testingPairs from '@plugnet/keyring/testingPairs';
|
||||||
|
* const keyring = testingPairs();
|
||||||
|
*
|
||||||
|
* // get api via Promise
|
||||||
|
* const api = await ApiRx.create().toPromise();
|
||||||
|
*
|
||||||
|
* // retrieve nonce for the account
|
||||||
|
* api.query.system
|
||||||
|
* .accountNonce(keyring.alice.address())
|
||||||
|
* .pipe(
|
||||||
|
* first(),
|
||||||
|
* // pipe nonce into transfer
|
||||||
|
* switchMap((nonce) =>
|
||||||
|
* api.tx.balances
|
||||||
|
* // create transfer
|
||||||
|
* .transfer(keyring.bob.address(), 12345)
|
||||||
|
* // sign the transcation
|
||||||
|
* .sign(keyring.alice, { nonce })
|
||||||
|
* // send the transaction
|
||||||
|
* .send()
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* // subscribe to overall result
|
||||||
|
* .subscribe(({ status }) => {
|
||||||
|
* if (status.isFinalized) {
|
||||||
|
* console.log('Completed at block hash', status.asFinalized.toHex());
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export default class ApiRx extends ApiBase<RxResult, RxResult> implements ApiRxInterface {
|
||||||
|
private _isReadyRx: Observable<ApiRx>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Creates an ApiRx instance using the supplied provider. Returns an Observable containing the actual Api instance.
|
||||||
|
*
|
||||||
|
* @param options options that is passed to the class contructor. Can be either [[ApiOptions]] or [[WsProvider]]
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* import { switchMap } from 'rxjs/operators';
|
||||||
|
* import Api from '@plugnet/api/rx';
|
||||||
|
*
|
||||||
|
* Api.create()
|
||||||
|
* .pipe(
|
||||||
|
* switchMap((api) =>
|
||||||
|
* api.rpc.chain.subscribeNewHead()
|
||||||
|
* ))
|
||||||
|
* .subscribe((header) => {
|
||||||
|
* console.log(`new block #${header.blockNumber.toNumber()}`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
static create (options?: ApiOptions | ProviderInterface): Observable<ApiRx> {
|
||||||
|
return new ApiRx(options).isReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Create an instance of the ApiRx class
|
||||||
|
*
|
||||||
|
* @param options Options to create an instance. Can be either [[ApiOptions]] or [[WsProvider]]
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <BR>
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* import { switchMap } from 'rxjs/operators';
|
||||||
|
* import Api from '@plugnet/api/rx';
|
||||||
|
*
|
||||||
|
* new Api().isReady
|
||||||
|
* .pipe(
|
||||||
|
* switchMap((api) =>
|
||||||
|
* api.rpc.chain.subscribeNewHead()
|
||||||
|
* ))
|
||||||
|
* .subscribe((header) => {
|
||||||
|
* console.log(`new block #${header.blockNumber.toNumber()}`);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
constructor (provider?: ApiOptions | ProviderInterface) {
|
||||||
|
super(provider, 'rxjs');
|
||||||
|
|
||||||
|
this._isReadyRx = from(
|
||||||
|
// convinced you can observable from an event, however my mind groks this form better
|
||||||
|
new Promise((resolveReady) =>
|
||||||
|
super.on('ready', () =>
|
||||||
|
resolveReady(this)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) as Observable<ApiRx>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Observable that carries the connected state for the provider. Results in a boolean flag that is true/false based on the connectivity.
|
||||||
|
*/
|
||||||
|
get isConnected (): Observable<boolean> {
|
||||||
|
return this._rpcRx.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Observable that returns the first time we are connected and loaded
|
||||||
|
*/
|
||||||
|
get isReady (): Observable<ApiRx> {
|
||||||
|
return this._isReadyRx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Returns a clone of this ApiRx instance (new underlying provider connection)
|
||||||
|
*/
|
||||||
|
clone (): ApiRx {
|
||||||
|
return new ApiRx({
|
||||||
|
...this._options,
|
||||||
|
source: this
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onCall (method: OnCallFunction<RxResult, RxResult>, params: Array<CodecArg> = []): RxResult {
|
||||||
|
return method(...params);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { default as ApiRx } from './Api';
|
||||||
|
|
||||||
|
export default ApiRx;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { Codec } from '@plugnet/types/types';
|
||||||
|
|
||||||
|
import ApiBase from '../Base';
|
||||||
|
|
||||||
|
export type RxResult = Observable<Codec>;
|
||||||
|
|
||||||
|
export interface ApiRxInterface extends ApiBase<RxResult, RxResult> {
|
||||||
|
readonly isConnected: Observable<boolean>;
|
||||||
|
readonly isReady: Observable<ApiRxInterface>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { DeriveCustom } from '@plugnet/api-derive';
|
||||||
|
import { ProviderInterface } from '@plugnet/rpc-provider/types';
|
||||||
|
import { RpcRxInterface$Events } from '@plugnet/rpc-rx/types';
|
||||||
|
import { Hash, Metadata, RuntimeVersion, u64 as U64 } from '@plugnet/types';
|
||||||
|
import { CodecArg, CodecCallback, IExtrinsic, RegistryTypes, SignatureOptions } from '@plugnet/types/types';
|
||||||
|
import { MethodFunction } from '@plugnet/types/primitive/Method';
|
||||||
|
import { StorageFunction } from '@plugnet/types/primitive/StorageKey';
|
||||||
|
|
||||||
|
import { RxResult } from './rx/types';
|
||||||
|
import { SubmittableResult, SubmittableExtrinsic } from './SubmittableExtrinsic';
|
||||||
|
import ApiBase from './Base';
|
||||||
|
|
||||||
|
export type OnCallDefinition<CodecResult, SubscriptionResult> = (method: OnCallFunction<RxResult, RxResult>, params?: Array<CodecArg>, callback?: CodecCallback, needsCallback?: boolean) => CodecResult | SubscriptionResult;
|
||||||
|
|
||||||
|
export type OnCallFunction<CodecResult, SubscriptionResult> = (...params: Array<CodecArg>) => CodecResult | SubscriptionResult;
|
||||||
|
|
||||||
|
// checked against max. params in jsonrpc, 1 for subs, 3 without
|
||||||
|
export interface DecoratedRpc$Method<CodecResult, SubscriptionResult> {
|
||||||
|
(callback: CodecCallback): SubscriptionResult;
|
||||||
|
(arg1: CodecArg, callback: CodecCallback): SubscriptionResult;
|
||||||
|
(arg1?: CodecArg, arg2?: CodecArg, arg3?: CodecArg): CodecResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DecoratedRpc$Section<CodecResult, SubscriptionResult> {
|
||||||
|
[index: string]: DecoratedRpc$Method<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DecoratedRpc<CodecResult, SubscriptionResult> {
|
||||||
|
author: DecoratedRpc$Section<CodecResult, SubscriptionResult>;
|
||||||
|
chain: DecoratedRpc$Section<CodecResult, SubscriptionResult>;
|
||||||
|
state: DecoratedRpc$Section<CodecResult, SubscriptionResult>;
|
||||||
|
system: DecoratedRpc$Section<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HashResult<CodecResult, SubscriptionResult> =
|
||||||
|
CodecResult extends Observable<any>
|
||||||
|
? Observable<Hash>
|
||||||
|
: Promise<Hash>;
|
||||||
|
|
||||||
|
export type U64Result<CodecResult, SubscriptionResult> =
|
||||||
|
CodecResult extends Observable<any>
|
||||||
|
? Observable<U64>
|
||||||
|
: Promise<U64>;
|
||||||
|
|
||||||
|
export interface QueryableStorageFunctionBase<CodecResult, SubscriptionResult> extends StorageFunction {
|
||||||
|
(arg?: CodecArg): CodecResult;
|
||||||
|
at: (hash: Hash | Uint8Array | string, arg?: CodecArg) => CodecResult;
|
||||||
|
hash: (arg?: CodecArg) => HashResult<CodecResult, SubscriptionResult>;
|
||||||
|
key: (arg?: CodecArg) => string;
|
||||||
|
size: (arg?: CodecArg) => U64Result<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryableStorageFunctionPromise<CodecResult, SubscriptionResult> extends QueryableStorageFunctionBase<CodecResult, SubscriptionResult> {
|
||||||
|
(callback: CodecCallback): SubscriptionResult;
|
||||||
|
(arg: CodecArg, callback: CodecCallback): SubscriptionResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QueryableStorageFunction<CodecResult, SubscriptionResult> =
|
||||||
|
CodecResult extends Observable<any>
|
||||||
|
? QueryableStorageFunctionBase<CodecResult, SubscriptionResult>
|
||||||
|
: QueryableStorageFunctionPromise<CodecResult, SubscriptionResult>;
|
||||||
|
|
||||||
|
export interface QueryableModuleStorage<CodecResult, SubscriptionResult> {
|
||||||
|
[index: string]: QueryableStorageFunction<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryableStorage<CodecResult, SubscriptionResult> {
|
||||||
|
[index: string]: QueryableModuleStorage<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubmittableExtrinsicFunction<CodecResult, SubscriptionResult> extends MethodFunction {
|
||||||
|
(...params: Array<CodecArg>): SubmittableExtrinsic<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubmittableModuleExtrinsics<CodecResult, SubscriptionResult> {
|
||||||
|
[index: string]: SubmittableExtrinsicFunction<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SubmittableExtrinsics<CodecResult, SubscriptionResult> {
|
||||||
|
[index: string]: SubmittableModuleExtrinsics<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeriveMethodBase<CodecResult, SubscriptionResult> {
|
||||||
|
(...params: Array<CodecArg>): CodecResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeriveMethodPromise<CodecResult, SubscriptionResult> extends DeriveMethodBase<CodecResult, SubscriptionResult> {
|
||||||
|
(callback: CodecCallback): SubscriptionResult;
|
||||||
|
(arg0: CodecArg, callback: CodecCallback): SubscriptionResult;
|
||||||
|
(arg0: CodecArg, arg1: CodecArg, callback: CodecCallback): SubscriptionResult;
|
||||||
|
(arg0: CodecArg, arg1: CodecArg, arg2: CodecArg, callback: CodecCallback): SubscriptionResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeriveMethod<CodecResult, SubscriptionResult> =
|
||||||
|
CodecResult extends Observable<any>
|
||||||
|
? DeriveMethodBase<CodecResult, SubscriptionResult>
|
||||||
|
: DeriveMethodPromise<CodecResult, SubscriptionResult>;
|
||||||
|
|
||||||
|
export interface DeriveSection<CodecResult, SubscriptionResult> {
|
||||||
|
[index: string]: DeriveMethod<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Derive<CodecResult, SubscriptionResult> {
|
||||||
|
[index: string]: DeriveSection<CodecResult, SubscriptionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiOptions {
|
||||||
|
/**
|
||||||
|
* @description Add custom derives to be injected
|
||||||
|
*/
|
||||||
|
derives?: DeriveCustom;
|
||||||
|
/**
|
||||||
|
* @description Transport Provider from rpc-provider. If not specified, it will default to
|
||||||
|
* connecting to a WsProvider connecting localhost with the default port, i.e. `ws://127.0.0.1:9944`
|
||||||
|
*/
|
||||||
|
provider?: ProviderInterface;
|
||||||
|
/**
|
||||||
|
* @description An external signer which will be used to sign extrinsic when account passed in is not KeyringPair
|
||||||
|
*/
|
||||||
|
signer?: Signer;
|
||||||
|
/**
|
||||||
|
* @description The source object to use for runtime information (only used when cloning)
|
||||||
|
*/
|
||||||
|
source?: ApiBase<any, any>;
|
||||||
|
/**
|
||||||
|
* @description Additional types used by runtime modules. This is nessusary if the runtime modules
|
||||||
|
* uses types not available in the base Substrate runtime.
|
||||||
|
*/
|
||||||
|
types?: RegistryTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiInterface$Decorated<CodecResult, SubscriptionResult> {
|
||||||
|
genesisHash: Hash;
|
||||||
|
hasSubscriptions: boolean;
|
||||||
|
runtimeMetadata: Metadata;
|
||||||
|
runtimeVersion: RuntimeVersion;
|
||||||
|
derive: Derive<CodecResult, SubscriptionResult>;
|
||||||
|
query: QueryableStorage<CodecResult, SubscriptionResult>;
|
||||||
|
rpc: DecoratedRpc<CodecResult, SubscriptionResult>;
|
||||||
|
tx: SubmittableExtrinsics<CodecResult, SubscriptionResult>;
|
||||||
|
signer?: Signer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiInterface$Rx = ApiInterface$Decorated<RxResult, RxResult>;
|
||||||
|
|
||||||
|
export type ApiInterface$Events = RpcRxInterface$Events | 'ready';
|
||||||
|
|
||||||
|
export type ApiType = 'promise' | 'rxjs';
|
||||||
|
|
||||||
|
export interface ApiBaseInterface<CodecResult, SubscriptionResult> extends Readonly<ApiInterface$Decorated<CodecResult, SubscriptionResult>> {
|
||||||
|
readonly type: ApiType;
|
||||||
|
|
||||||
|
on: (type: ApiInterface$Events, handler: (...args: Array<any>) => any) => this;
|
||||||
|
once: (type: ApiInterface$Events, handler: (...args: Array<any>) => any) => this;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Signer {
|
||||||
|
/**
|
||||||
|
* @description Signs an extrinsic, returning an id (>0) that can be used to retrieve updates
|
||||||
|
*/
|
||||||
|
sign (extrinsic: IExtrinsic, address: string, options: SignatureOptions): Promise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Receives an update for the extrinsic signed by a `signer.sign`
|
||||||
|
*/
|
||||||
|
update?: (id: number, status: Hash | SubmittableResult) => void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { EventRecord, SignedBlock, U8a } from '@plugnet/types';
|
||||||
|
|
||||||
|
import l from './logging';
|
||||||
|
|
||||||
|
export default function filterEvents (extHash: U8a, { block: { extrinsics, header } }: SignedBlock, allEvents: Array<EventRecord>): Array<EventRecord> | undefined {
|
||||||
|
// extrinsics to hashes
|
||||||
|
const myHash = extHash.toHex();
|
||||||
|
const allHashes = extrinsics.map((ext) => ext.hash.toHex());
|
||||||
|
|
||||||
|
// find the index of our extrinsic in the block
|
||||||
|
const index = allHashes.indexOf(myHash);
|
||||||
|
|
||||||
|
// if we do get the block after finalized, it _should_ be there
|
||||||
|
if (index === -1) {
|
||||||
|
l.warn(`block ${header.hash}: Unable to find extrinsic ${myHash} inside ${allHashes}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allEvents.filter(({ phase }) =>
|
||||||
|
// only ApplyExtrinsic has the extrinsic index
|
||||||
|
phase.isApplyExtrinsic && phase.asApplyExtrinsic.eqn(index)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { logger } from '@plugnet/util';
|
||||||
|
|
||||||
|
const l = logger('api/util');
|
||||||
|
|
||||||
|
export default l;
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"name": "Erc20",
|
||||||
|
"deploy": {
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "init_value",
|
||||||
|
"type": "Balance"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"name": "total_supply",
|
||||||
|
"selector": 949181842,
|
||||||
|
"mutates": false,
|
||||||
|
"args": [],
|
||||||
|
"return_type": "Balance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "balance_of",
|
||||||
|
"selector": 3827153702,
|
||||||
|
"mutates": false,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "Address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"return_type": "Balance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "allowance",
|
||||||
|
"selector": 3057219184,
|
||||||
|
"mutates": false,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spender",
|
||||||
|
"type": "Address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"return_type": "Balance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "transfer",
|
||||||
|
"selector": 3374842663,
|
||||||
|
"mutates": true,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "to",
|
||||||
|
"type": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "value",
|
||||||
|
"type": "Balance"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"return_type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "approve",
|
||||||
|
"selector": 431186122,
|
||||||
|
"mutates": true,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "spender",
|
||||||
|
"type": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "value",
|
||||||
|
"type": "Balance"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"return_type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "transfer_from",
|
||||||
|
"selector": 197482595,
|
||||||
|
"mutates": true,
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "from",
|
||||||
|
"type": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "to",
|
||||||
|
"type": "Address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "value",
|
||||||
|
"type": "Balance"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"return_type": "bool"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import Api from '../../src/promise';
|
||||||
|
import WsProvider from '../../../rpc-provider/src/ws';
|
||||||
|
|
||||||
|
describe.skip('alex queries', () => {
|
||||||
|
let api;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
api = await Api.create({
|
||||||
|
provider: new WsProvider('wss://poc3-rpc.polkadot.io/')
|
||||||
|
});
|
||||||
|
|
||||||
|
return api;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.setTimeout(3000000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retrieves the list of validators', (done) => {
|
||||||
|
api.query.staking.validators((res) => {
|
||||||
|
console.log('api.query.staking.validators():', res.toJSON());
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retrieves a single value', (done) => {
|
||||||
|
api.query.staking.validators('5EF7wdkP9XZq38fu2ioAvxoEhHUJUc1E2KbuSQSMGofRKhCL', (res) => {
|
||||||
|
console.log('api.query.staking.validators(id):', res.toJSON());
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('derives a list of the controllers', (done) => {
|
||||||
|
api.derive.staking.controllers((res) => {
|
||||||
|
console.log('api.derive.staking.controllers:', JSON.stringify(res));
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('retrieves the list of nominators', (done) => {
|
||||||
|
let count = 0;
|
||||||
|
api.query.staking.nominators((res) => {
|
||||||
|
console.log(`[${++count}]:: nominators(${res[0].length}):`, res.toJSON());
|
||||||
|
|
||||||
|
// done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
import { ContractAbi } from '@polkadot/types';
|
||||||
|
import testingPairs from '@polkadot/keyring/testingPairs';
|
||||||
|
|
||||||
|
import json from '../data/erc20.json';
|
||||||
|
import Api from '../../src/promise';
|
||||||
|
|
||||||
|
describe.skip('e2e contracts', () => {
|
||||||
|
let address;
|
||||||
|
let codeHash;
|
||||||
|
let keyring;
|
||||||
|
let abi;
|
||||||
|
let api;
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
if (!api) {
|
||||||
|
api = await Api.create();
|
||||||
|
|
||||||
|
abi = new ContractAbi(json);
|
||||||
|
keyring = testingPairs({ type: 'sr25519' });
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.setTimeout(10000);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has the attached methods', () => {
|
||||||
|
expect(Object.keys(abi.messages)).toEqual(
|
||||||
|
['totalSupply', 'balanceOf', 'allowance', 'transfer', 'approve', 'transferFrom']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows putCode', (done) => {
|
||||||
|
const code = fs.readFileSync(path.join(__dirname, '../data/erc20.wasm')).toString('hex');
|
||||||
|
|
||||||
|
api.tx.contract
|
||||||
|
.putCode(200000, `0x${code}`)
|
||||||
|
.signAndSend(keyring.eve, (result) => {
|
||||||
|
console.error('putCode', JSON.stringify(result));
|
||||||
|
|
||||||
|
if (result.status.isFinalized) {
|
||||||
|
const record = result.findRecord('contract', 'CodeStored');
|
||||||
|
|
||||||
|
if (record) {
|
||||||
|
codeHash = record.event.data[0];
|
||||||
|
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows contract create', (done) => {
|
||||||
|
expect(codeHash).toBeDefined();
|
||||||
|
|
||||||
|
api.tx.contract
|
||||||
|
.create(1234, 12345, codeHash, abi.deploy(12345))
|
||||||
|
.signAndSend(keyring.bob, (result) => {
|
||||||
|
console.error('create', JSON.stringify(result));
|
||||||
|
|
||||||
|
if (result.status.isFinalized) {
|
||||||
|
const record = result.findRecord('contract', 'Instantiated');
|
||||||
|
|
||||||
|
if (record) {
|
||||||
|
address = record.event.data[1];
|
||||||
|
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows contract call', (done) => {
|
||||||
|
expect(address).toBeDefined();
|
||||||
|
|
||||||
|
api.tx.contract
|
||||||
|
.call(address, 123, 12345, abi.messages.balanceOf(keyring.alice.address()))
|
||||||
|
.signAndSend(keyring.bob, (result) => {
|
||||||
|
console.error('call', JSON.stringify(result));
|
||||||
|
|
||||||
|
if (result.status.isFinalized && result.findRecord('system', 'ExtrinsicSuccess')) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import BN from 'bn.js';
|
||||||
|
import testingPairs from '@polkadot/keyring/testingPairs';
|
||||||
|
|
||||||
|
import Api from '../../src/promise';
|
||||||
|
|
||||||
|
describe.skip('e2e queries', () => {
|
||||||
|
const keyring = testingPairs({ type: 'ed25519' });
|
||||||
|
let api;
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
if (!api) {
|
||||||
|
api = await Api.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes the runtime, rpc, state & extrinsics available', () => {
|
||||||
|
expect(api.genesisHash).toBeDefined();
|
||||||
|
expect(api.runtimeMetadata).toBeDefined();
|
||||||
|
expect(api.runtimeVersion).toBeDefined();
|
||||||
|
expect(api.rpc).toBeDefined();
|
||||||
|
expect(api.query).toBeDefined();
|
||||||
|
expect(api.tx).toBeDefined();
|
||||||
|
expect(api.derive).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('queries state for a balance', async () => {
|
||||||
|
const balance = await api.query.balances.freeBalance(keyring.alice.address());
|
||||||
|
|
||||||
|
expect(
|
||||||
|
balance.isZero()
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subscribes to rpc', (done) => {
|
||||||
|
api.rpc.chain.subscribeNewHead((header) => {
|
||||||
|
expect(header.blockNumber.isZero()).toBe(false);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subscribes to finalized', (done) => {
|
||||||
|
api.rpc.chain.subscribeFinalizedHeads((header) => {
|
||||||
|
expect(header.blockNumber.isZero()).toBe(false);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subscribes to derive', (done) => {
|
||||||
|
api.derive.chain.subscribeNewHead((header) => {
|
||||||
|
expect(header.blockNumber.isZero()).toBe(false);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('subscribes to queries', (done) => {
|
||||||
|
api.query.system.accountNonce(keyring.ferdie.address(), (nonce) => {
|
||||||
|
expect(nonce instanceof BN).toBe(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('subscribes to queries (default)', (done) => {
|
||||||
|
api.query.staking.validators(keyring.ferdie.address(), (prefs) => {
|
||||||
|
expect(prefs.unstakeThreshold.toNumber()).toBe(3);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a query at a specific block', async () => {
|
||||||
|
const header = await api.rpc.chain.getHeader();
|
||||||
|
const events = await api.query.system.events.at(header.hash);
|
||||||
|
|
||||||
|
expect(events.length).not.toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import Keyring from '@polkadot/keyring';
|
||||||
|
import testingPairs from '@polkadot/keyring/testingPairs';
|
||||||
|
import { randomAsHex } from '@polkadot/util-crypto';
|
||||||
|
|
||||||
|
import Api from '../../src/promise';
|
||||||
|
import WsProvider from '../../../rpc-provider/src/ws';
|
||||||
|
import SingleAccountSigner from "../util/SingleAccountSigner";
|
||||||
|
|
||||||
|
describe.skip('e2e transactions', () => {
|
||||||
|
const keyring = testingPairs({ type: 'ed25519' });
|
||||||
|
let api;
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
if (!api) {
|
||||||
|
api = await Api.create({
|
||||||
|
provider: new WsProvider('ws://127.0.0.1:9944')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.setTimeout(5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a transfer (sign, then send)', async (done) => {
|
||||||
|
const nonce = await api.query.system.accountNonce(keyring.dave.address());
|
||||||
|
|
||||||
|
return api.tx.balances
|
||||||
|
.transfer(keyring.eve.address(), 12345)
|
||||||
|
.sign(keyring.dave, { nonce })
|
||||||
|
.send(({ events, status }) => {
|
||||||
|
console.log('Transaction status:', status.type);
|
||||||
|
|
||||||
|
if (status.isFinalized) {
|
||||||
|
console.log('Completed at block hash', status.value.toHex());
|
||||||
|
console.log('Events:');
|
||||||
|
|
||||||
|
events.forEach(({ phase, event: { data, method, section } }) => {
|
||||||
|
console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (events.length) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a transfer (sign, then send - compat version)', async (done) => {
|
||||||
|
const nonce = await api.query.system.accountNonce(keyring.dave.address());
|
||||||
|
|
||||||
|
return api.tx.balances
|
||||||
|
.transfer(keyring.eve.address(), 12345)
|
||||||
|
.sign(keyring.dave, nonce)
|
||||||
|
.send(({ events, status }) => {
|
||||||
|
console.log('Transaction status:', status.type);
|
||||||
|
|
||||||
|
if (status.isFinalized) {
|
||||||
|
console.log('Completed at block hash', status.value.toHex());
|
||||||
|
console.log('Events:');
|
||||||
|
|
||||||
|
events.forEach(({ phase, event: { data, method, section } }) => {
|
||||||
|
console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (events.length) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a transfer (signAndSend)', async (done) => {
|
||||||
|
return api.tx.balances
|
||||||
|
.transfer(keyring.eve.address(), 12345)
|
||||||
|
.signAndSend(keyring.dave, ({ events, status }) => {
|
||||||
|
console.log('Transaction status:', status.type);
|
||||||
|
|
||||||
|
if (status.isFinalized) {
|
||||||
|
console.log('Completed at block hash', status.value.toHex());
|
||||||
|
console.log('Events:');
|
||||||
|
|
||||||
|
events.forEach(({ phase, event: { data, method, section } }) => {
|
||||||
|
console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (events.length) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a transfer (signAndSend via Signer)', async (done) => {
|
||||||
|
const signer = new SingleAccountSigner(keyring.dave);
|
||||||
|
|
||||||
|
api.setSigner(signer);
|
||||||
|
|
||||||
|
return api.tx.balances
|
||||||
|
.transfer(keyring.eve.address(), 12345)
|
||||||
|
.signAndSend(keyring.dave.address(), ({ events, status }) => {
|
||||||
|
console.log('Transaction status:', status.type);
|
||||||
|
|
||||||
|
if (status.isFinalized) {
|
||||||
|
console.log('Completed at block hash', status.value.toHex());
|
||||||
|
console.log('Events:');
|
||||||
|
|
||||||
|
events.forEach(({ phase, event: { data, method, section } }) => {
|
||||||
|
console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (events.length) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a transfer (signAndSend via Signer) - sad path', async () => {
|
||||||
|
//no signer
|
||||||
|
api.setSigner();
|
||||||
|
|
||||||
|
await expect(api.tx.balances
|
||||||
|
.transfer(keyring.eve.address(), 12345)
|
||||||
|
.signAndSend(keyring.alice.address())).rejects.toThrow('no signer exists');
|
||||||
|
|
||||||
|
const signer = new SingleAccountSigner(keyring.dave);
|
||||||
|
|
||||||
|
api.setSigner(signer);
|
||||||
|
|
||||||
|
//no callback
|
||||||
|
await expect(api.tx.balances
|
||||||
|
.transfer(keyring.eve.address(), 12345)
|
||||||
|
.signAndSend(keyring.alice.address())).rejects.toThrow('does not have the keyringPair');
|
||||||
|
|
||||||
|
//with callback
|
||||||
|
await expect(api.tx.balances
|
||||||
|
.transfer(keyring.eve.address(), 12345)
|
||||||
|
.signAndSend(keyring.alice.address(), ({ events, status }) => {
|
||||||
|
})).rejects.toThrow('does not have the keyringPair');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a transfer (no callback)', async () => {
|
||||||
|
const hash = await api.tx.balances
|
||||||
|
.transfer(keyring.eve.address(), 12345)
|
||||||
|
.signAndSend(keyring.dave);
|
||||||
|
|
||||||
|
expect(hash.toHex()).toHaveLength(66);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a proposal', async () => {
|
||||||
|
// don't wait for status, just get hash. Here we generate a large-ish payload
|
||||||
|
// to ensure that we can sign with the hashed version as well (and have it accepted)
|
||||||
|
const hash = await api.tx.democracy
|
||||||
|
.propose(api.tx.consensus.setCode(randomAsHex(4096)), 10000)
|
||||||
|
.signAndSend(keyring.bob);
|
||||||
|
|
||||||
|
expect(hash.toHex()).toHaveLength(66);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a transfer, and uses new balance to transfers to new', async (done) => {
|
||||||
|
const pair = new Keyring().addFromUri('testing123', {}, 'ed25519');
|
||||||
|
|
||||||
|
function doOne (cb) {
|
||||||
|
return api.tx.balances
|
||||||
|
.transfer(pair.address(), 123456)
|
||||||
|
.signAndSend(keyring.dave, ({ events, status }) => {
|
||||||
|
console.log('One: Transaction status:', status.type);
|
||||||
|
|
||||||
|
if (status.isFinalized) {
|
||||||
|
console.log('Completed at block hash', status.value.toHex());
|
||||||
|
console.log('Events:');
|
||||||
|
|
||||||
|
events.forEach(({ phase, event: { data, method, section } }) => {
|
||||||
|
console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function doTwo (cb) {
|
||||||
|
return api.tx.balances
|
||||||
|
.transfer(keyring.alice.address(), 12345)
|
||||||
|
.signAndSend(pair, ({ events, status }) => {
|
||||||
|
console.log('One: Transaction status:', status.type);
|
||||||
|
|
||||||
|
if (status.isFinalized) {
|
||||||
|
console.log('Completed at block hash', status.value.toHex());
|
||||||
|
console.log('Events:');
|
||||||
|
|
||||||
|
events.forEach(({ phase, event: { data, method, section } }) => {
|
||||||
|
console.log('\t', phase.toString(), `: ${section}.${method}`, data.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// return doTwo(done);
|
||||||
|
return doOne(() => {
|
||||||
|
doTwo(done)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import testingPairs from '@polkadot/keyring/testingPairs';
|
||||||
|
|
||||||
|
import Api from '../../src/rx';
|
||||||
|
|
||||||
|
describe.skip('e2e queries', () => {
|
||||||
|
const keyring = testingPairs({ type: 'ed25519' });
|
||||||
|
let api;
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
api = await Api.create().toPromise();
|
||||||
|
jest.setTimeout(3000000);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes the runtime, rpc, state & extrinsics available', () => {
|
||||||
|
expect(api.genesisHash).toBeDefined();
|
||||||
|
expect(api.runtimeMetadata).toBeDefined();
|
||||||
|
expect(api.runtimeVersion).toBeDefined();
|
||||||
|
expect(api.rpc).toBeDefined();
|
||||||
|
expect(api.query).toBeDefined();
|
||||||
|
expect(api.tx).toBeDefined();
|
||||||
|
expect(api.derive).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('queries state for a balance', (done) => {
|
||||||
|
api.query.balances.freeBalance(keyring.alice.address()).subscribe((balance) => {
|
||||||
|
expect(
|
||||||
|
balance.isZero()
|
||||||
|
).toBe(false);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a query at a specific block', (done) => {
|
||||||
|
api.rpc.chain
|
||||||
|
.getHeader()
|
||||||
|
.pipe(
|
||||||
|
switchMap((header) =>
|
||||||
|
api.query.system.events.at(header.hash)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.subscribe((events) => {
|
||||||
|
expect(events.length).not.toEqual(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { first, switchMap } from 'rxjs/operators';
|
||||||
|
import testingPairs from '@polkadot/keyring/testingPairs';
|
||||||
|
|
||||||
|
import Api from '../../src/rx';
|
||||||
|
|
||||||
|
describe.skip('e2e transactions', () => {
|
||||||
|
const keyring = testingPairs({ type: 'ed25519' });
|
||||||
|
let api;
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
api = await Api.create().toPromise();
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
done()
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.setTimeout(5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a transfer', (done) => {
|
||||||
|
api.query.system
|
||||||
|
.accountNonce(keyring.alice.address())
|
||||||
|
.pipe(
|
||||||
|
first(),
|
||||||
|
switchMap((nonce) =>
|
||||||
|
api.tx.balances
|
||||||
|
.transfer(keyring.bob.address(), 12345)
|
||||||
|
.sign(keyring.alice, { nonce })
|
||||||
|
.send()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.subscribe(({ status }) => {
|
||||||
|
if (status.isFinalized) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('makes a proposal', (done) => {
|
||||||
|
api.query.system
|
||||||
|
.accountNonce(keyring.alice.address())
|
||||||
|
.pipe(
|
||||||
|
first(),
|
||||||
|
switchMap((nonce) =>
|
||||||
|
api.tx.democracy
|
||||||
|
.propose(api.tx.consensus.setCode('0xdeadbeef'), 10000)
|
||||||
|
.sign(keyring.alice, { nonce })
|
||||||
|
.send()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.subscribe(({ status }) => {
|
||||||
|
if (status.isFinalized) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/api authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { KeyringPair } from '@plugnet/keyring/types';
|
||||||
|
import { SignatureOptions } from '@plugnet/types/types';
|
||||||
|
|
||||||
|
import { Extrinsic } from '@plugnet/types';
|
||||||
|
|
||||||
|
let id = 0;
|
||||||
|
|
||||||
|
export default class SingleAccountSigner {
|
||||||
|
private keyringPair: KeyringPair;
|
||||||
|
|
||||||
|
constructor (keyringPair: KeyringPair) {
|
||||||
|
this.keyringPair = keyringPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sign (extrinsic: Extrinsic, address: string, options: SignatureOptions) {
|
||||||
|
if (!this.keyringPair || String(address) !== this.keyringPair.address()) {
|
||||||
|
throw new Error('does not have the keyringPair');
|
||||||
|
}
|
||||||
|
|
||||||
|
extrinsic.sign(this.keyringPair, options);
|
||||||
|
|
||||||
|
return ++id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# @polkadot/keyring
|
||||||
|
|
||||||
|
Key management of user accounts including generation and retrieval of keyring pairs from a variety of input combinations.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Installation -
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn add @polkadot/keyring
|
||||||
|
```
|
||||||
|
|
||||||
|
Classes and Functions can be imported as follows:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Keyring from '@polkadot/keyring';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Utilities
|
||||||
|
|
||||||
|
[Utilities](SUMMARY.md)
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "@plugnet/keyring",
|
||||||
|
"version": "0.76.1",
|
||||||
|
"description": "Keyring management",
|
||||||
|
"main": "index.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/polkadot-js/common.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Plugnet",
|
||||||
|
"Keyring",
|
||||||
|
"ed25519"
|
||||||
|
],
|
||||||
|
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||||
|
"maintainers": [
|
||||||
|
"Jaco Greeff <jacogr@gmail.com>"
|
||||||
|
],
|
||||||
|
"contributors": [],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/polkadot-js/common/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/keyring#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.4.3",
|
||||||
|
"@plugnet/util": "^0.76.1",
|
||||||
|
"@plugnet/util-crypto": "^0.76.1",
|
||||||
|
"@types/bs58": "^4.0.0",
|
||||||
|
"bs58": "^4.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import testingPairs from '../testingPairs';
|
||||||
|
import decode from './decode';
|
||||||
|
|
||||||
|
describe('decode', () => {
|
||||||
|
let keyring: any;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
keyring = testingPairs({ type: 'sr25519' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decodes an address', () => {
|
||||||
|
expect(
|
||||||
|
decode('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY')
|
||||||
|
).toEqual(
|
||||||
|
keyring.alice.publicKey()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts a publicKey (u8a) as-is', () => {
|
||||||
|
expect(
|
||||||
|
decode(new Uint8Array([1, 2, 3]))
|
||||||
|
).toEqual(
|
||||||
|
new Uint8Array([1, 2, 3])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts a publicKey (hex) as-is', () => {
|
||||||
|
expect(
|
||||||
|
decode('0x01020304')
|
||||||
|
).toEqual(
|
||||||
|
new Uint8Array([1, 2, 3, 4])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decodes a short address', () => {
|
||||||
|
expect(
|
||||||
|
decode('F7NZ')
|
||||||
|
).toEqual(new Uint8Array([1]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decodes a 1-byte accountId (with prefix)', () => {
|
||||||
|
expect(
|
||||||
|
decode('PqtB', false, 68)
|
||||||
|
).toEqual(new Uint8Array([1]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decodes a 2-byte accountId', () => {
|
||||||
|
expect(
|
||||||
|
decode('2jpAFn', false, 68)
|
||||||
|
).toEqual(new Uint8Array([0, 1]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('encodes a 4-byte address', () => {
|
||||||
|
expect(
|
||||||
|
decode('as7QnGMf', false, 68)
|
||||||
|
).toEqual(new Uint8Array([1, 2, 3, 4]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decodes a 8-byte address', () => {
|
||||||
|
expect(
|
||||||
|
decode('4q7qY5RBG7Z4wv', false, 68)
|
||||||
|
).toEqual(new Uint8Array([42, 44, 10, 0, 0, 0, 0, 0]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('allows invalid prefix (in list)', () => {
|
||||||
|
expect(
|
||||||
|
() => decode('6GfvWUvHvU8otbZ7sFhXH4eYeMcKdUkL61P3nFy52efEPVUx')
|
||||||
|
).toThrow(/address prefix/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails when length is invalid', () => {
|
||||||
|
expect(
|
||||||
|
() => decode('y9EMHt34JJo4rWLSaxoLGdYXvjgSXEd4zHUnQgfNzwES8b')
|
||||||
|
).toThrow(/address length/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails when the checksum does not match', () => {
|
||||||
|
expect(
|
||||||
|
() => decode('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMa9cj')
|
||||||
|
).toThrow(/address checksum/);
|
||||||
|
expect(
|
||||||
|
() => decode('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDwU')
|
||||||
|
).toThrow(/address checksum/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Prefix } from './types';
|
||||||
|
|
||||||
|
// Original implementation: https://github.com/paritytech/polka-ui/blob/4858c094684769080f5811f32b081dd7780b0880/src/polkadot.js#L6
|
||||||
|
|
||||||
|
import bs58 from 'bs58';
|
||||||
|
import { assert, bufferToU8a, isHex, isU8a, u8aToU8a } from '@plugnet/util';
|
||||||
|
|
||||||
|
import defaults from './defaults';
|
||||||
|
import sshash from './sshash';
|
||||||
|
|
||||||
|
export default function decode (encoded: string | Uint8Array, ignoreChecksum?: boolean, prefix: Prefix = defaults.prefix): Uint8Array {
|
||||||
|
if (isU8a(encoded) || isHex(encoded)) {
|
||||||
|
return u8aToU8a(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoded = bufferToU8a(bs58.decode(encoded));
|
||||||
|
const error = (message: string) =>
|
||||||
|
`Decoding ${encoded}: ${message}`;
|
||||||
|
|
||||||
|
// assert(defaults.allowedPrefix.includes(decoded[0] as Prefix), error('Invalid decoded address prefix'));
|
||||||
|
assert(defaults.allowedEncodedLengths.includes(decoded.length), error('Invalid decoded address length'));
|
||||||
|
|
||||||
|
// TODO Unless it is an "use everywhere" prefix, throw an error
|
||||||
|
// if (decoded[0] !== prefix) {
|
||||||
|
// console.log(`WARN: Expected ${prefix}, found ${decoded[0]}`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const isPublicKey = decoded.length === 35;
|
||||||
|
|
||||||
|
// non-publicKeys has 1 byte checksums, else default to 2
|
||||||
|
const endPos = decoded.length - (isPublicKey ? 2 : 1);
|
||||||
|
|
||||||
|
// calculate the hash and do the checksum byte checks
|
||||||
|
const hash = sshash(decoded.subarray(0, endPos));
|
||||||
|
const checks = isPublicKey
|
||||||
|
? decoded[decoded.length - 2] === hash[0] && decoded[decoded.length - 1] === hash[1]
|
||||||
|
: decoded[decoded.length - 1] === hash[0];
|
||||||
|
|
||||||
|
assert(ignoreChecksum || checks, error('Invalid decoded address checksum'));
|
||||||
|
|
||||||
|
return decoded.slice(1, endPos);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Prefix } from './types';
|
||||||
|
|
||||||
|
const defaults = {
|
||||||
|
allowedDecodedLengths: [1, 2, 4, 8, 32],
|
||||||
|
// publicKey has prefix + 2 checksum bytes, short only prefix + 1 checksum byte
|
||||||
|
allowedEncodedLengths: [3, 4, 6, 10, 35],
|
||||||
|
allowedPrefix: [0, 1, 3, 42, 43, 68, 69] as Array<Prefix>,
|
||||||
|
prefix: 42 as Prefix
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defaults;
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import testingPairs from '../testingPairs';
|
||||||
|
import encode from './encode';
|
||||||
|
|
||||||
|
const keyring = testingPairs({ type: 'ed25519' }, false);
|
||||||
|
|
||||||
|
describe('encode', () => {
|
||||||
|
it('encodes an address to a valid value', () => {
|
||||||
|
expect(
|
||||||
|
keyring.alice.address()
|
||||||
|
).toEqual('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails when non-valid publicKey provided', () => {
|
||||||
|
expect(
|
||||||
|
() => encode(
|
||||||
|
keyring.alice.publicKey().slice(0, 30)
|
||||||
|
)
|
||||||
|
).toThrow(/Expected a valid key/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('encodes a 1-byte address', () => {
|
||||||
|
expect(
|
||||||
|
encode(
|
||||||
|
new Uint8Array([1])
|
||||||
|
)
|
||||||
|
).toEqual('F7NZ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('encodes a 1-byte address (with prefix)', () => {
|
||||||
|
expect(
|
||||||
|
encode(
|
||||||
|
new Uint8Array([1]), 68
|
||||||
|
)
|
||||||
|
).toEqual('PqtB');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('encodes a 2-byte address', () => {
|
||||||
|
expect(
|
||||||
|
encode(
|
||||||
|
new Uint8Array([0, 1]), 68
|
||||||
|
)
|
||||||
|
).toEqual('2jpAFn');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('encodes a 4-byte address', () => {
|
||||||
|
expect(
|
||||||
|
encode(
|
||||||
|
new Uint8Array([1, 2, 3, 4]), 68
|
||||||
|
)
|
||||||
|
).toEqual('as7QnGMf');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enodes a 8-byte address', () => {
|
||||||
|
expect(
|
||||||
|
encode(
|
||||||
|
new Uint8Array([42, 44, 10, 0, 0, 0, 0, 0]), 68
|
||||||
|
)
|
||||||
|
).toEqual('4q7qY5RBG7Z4wv');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Prefix } from './types';
|
||||||
|
|
||||||
|
// Original implementation: https://github.com/paritytech/polka-ui/blob/4858c094684769080f5811f32b081dd7780b0880/src/polkadot.js#L34
|
||||||
|
|
||||||
|
import bs58 from 'bs58';
|
||||||
|
import { assert, u8aConcat, u8aToBuffer, u8aToU8a } from '@plugnet/util';
|
||||||
|
|
||||||
|
import defaults from './defaults';
|
||||||
|
import sshash from './sshash';
|
||||||
|
|
||||||
|
export default function encode (_key: Uint8Array | string, prefix: Prefix = defaults.prefix): string {
|
||||||
|
const key = u8aToU8a(_key);
|
||||||
|
|
||||||
|
assert(defaults.allowedDecodedLengths.includes(key.length), `Expected a valid key to convert, with length ${defaults.allowedDecodedLengths}`);
|
||||||
|
|
||||||
|
const isPublicKey = key.length === 32;
|
||||||
|
|
||||||
|
const input = u8aConcat(new Uint8Array([prefix]), key);
|
||||||
|
const hash = sshash(input);
|
||||||
|
|
||||||
|
return bs58.encode(
|
||||||
|
u8aToBuffer(
|
||||||
|
u8aConcat(input, hash.subarray(0, isPublicKey ? 2 : 1))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import decodeAddress from './decode';
|
||||||
|
import encodeAddress from './encode';
|
||||||
|
import setAddressPrefix from './setPrefix';
|
||||||
|
|
||||||
|
export {
|
||||||
|
decodeAddress,
|
||||||
|
encodeAddress,
|
||||||
|
setAddressPrefix
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import encode from './encode';
|
||||||
|
import setPrefix from './setPrefix';
|
||||||
|
|
||||||
|
describe('setPrefix', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setPrefix(68);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets and allows encoding using', () => {
|
||||||
|
expect(
|
||||||
|
encode(
|
||||||
|
new Uint8Array([1])
|
||||||
|
)
|
||||||
|
).toEqual('PqtB');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { Prefix } from './types';
|
||||||
|
|
||||||
|
import defaults from './defaults';
|
||||||
|
|
||||||
|
export default function setPrefix (prefix: Prefix): void {
|
||||||
|
defaults.prefix = prefix;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { stringToU8a, u8aConcat } from '@plugnet/util';
|
||||||
|
import { blake2AsU8a } from '@plugnet/util-crypto';
|
||||||
|
|
||||||
|
const SS58_PREFIX = stringToU8a('SS58PRE');
|
||||||
|
|
||||||
|
export default function hash (key: Uint8Array): Uint8Array {
|
||||||
|
return blake2AsU8a(u8aConcat(SS58_PREFIX, key), 512);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
export type Prefix = 0 | 1 | 3 | 42 | 43 | 68 | 69;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
// default substrate dev phrase
|
||||||
|
const DEV_PHRASE = 'bottom drive obey lake curtain smoke basket hold race lonely fit walk';
|
||||||
|
|
||||||
|
// seed from the above phrase
|
||||||
|
const DEV_SEED = '0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e';
|
||||||
|
|
||||||
|
export {
|
||||||
|
DEV_PHRASE,
|
||||||
|
DEV_SEED
|
||||||
|
};
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { hexToU8a, stringToU8a } from '@plugnet/util';
|
||||||
|
import { cryptoWaitReady } from '@plugnet/util-crypto';
|
||||||
|
|
||||||
|
import Keyring from '.';
|
||||||
|
import setPrefix from './address/setPrefix';
|
||||||
|
|
||||||
|
describe('keypair', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await cryptoWaitReady();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ed25519', () => {
|
||||||
|
const publicKeyOne = new Uint8Array([47, 140, 97, 41, 216, 22, 207, 81, 195, 116, 188, 127, 8, 195, 230, 62, 209, 86, 207, 120, 174, 251, 74, 101, 80, 217, 123, 135, 153, 121, 119, 238]);
|
||||||
|
const publicKeyTwo = new Uint8Array([215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114, 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26]);
|
||||||
|
const seedOne = stringToU8a('12345678901234567890123456789012');
|
||||||
|
const seedTwo = hexToU8a('0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60');
|
||||||
|
let keypair: Keyring;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
keypair = new Keyring({ type: 'ed25519' });
|
||||||
|
|
||||||
|
keypair.addFromSeed(seedOne, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds the pair', () => {
|
||||||
|
expect(
|
||||||
|
keypair.addFromSeed(seedTwo, {}).publicKey()
|
||||||
|
).toEqual(publicKeyTwo);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a ed25519 pair via mnemonicToSeed', () => {
|
||||||
|
expect(
|
||||||
|
keypair.addFromUri(
|
||||||
|
'seed sock milk update focus rotate barely fade car face mechanic mercy'
|
||||||
|
).address()
|
||||||
|
).toEqual('5DkQP32jP4DVJLWWBRBoZF2tpWjqFrcrTBo6H5NcSk7MxKCC');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds from a mnemonic', () => {
|
||||||
|
setPrefix(68);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
keypair.addFromMnemonic('moral movie very draw assault whisper awful rebuild speed purity repeat card', {}).address()
|
||||||
|
).toEqual('7sPsxWPE5DzAyPT3VuoJYw5NTGscx9QYN9oddQx4kALKC3hH');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows publicKeys retrieval', () => {
|
||||||
|
keypair.addFromSeed(seedTwo, {});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
keypair.getPublicKeys()
|
||||||
|
).toEqual([ publicKeyOne, publicKeyTwo ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows retrieval of a specific item', () => {
|
||||||
|
expect(
|
||||||
|
keypair.getPair(publicKeyOne).publicKey()
|
||||||
|
).toEqual(publicKeyOne);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows adding from JSON', () => {
|
||||||
|
expect(
|
||||||
|
keypair.addFromJson(
|
||||||
|
JSON.parse('{"address":"5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua","encoded":"0xb4a14995d25ab609f3686e9fa45f1fb237cd833f33f00d4b12c51858ca070d96972e47d73aae5eeb0fc06f923826cf0943fdb02c2c2ee30ef52a7912663053940d1da4da66b3a3f520ae07422c1c94b2d95690fca9d1f4a997623bb2923a8833280e19e7f72c3c5cfa343974e60e2b3dc53b404fdaf330756daad5e4e3","encoding":{"content":"pkcs8","type":"xsalsa20-poly1305","version":"0"},"meta":{"isTesting":true,"name":"alice"}}')
|
||||||
|
).publicKey()
|
||||||
|
).toEqual(
|
||||||
|
new Uint8Array([209, 114, 167, 76, 218, 76, 134, 89, 18, 195, 43, 160, 168, 10, 87, 174, 105, 171, 174, 65, 14, 92, 203, 89, 222, 232, 78, 47, 68, 50, 219, 79])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('signs and verifies', () => {
|
||||||
|
const MESSAGE = stringToU8a('this is a message');
|
||||||
|
const pair = keypair.getPair(publicKeyOne);
|
||||||
|
const signature = pair.sign(MESSAGE);
|
||||||
|
|
||||||
|
expect(pair.verify(MESSAGE, signature)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sr25519', () => {
|
||||||
|
const publicKeyOne = new Uint8Array([116, 28, 8, 160, 111, 65, 197, 150, 96, 143, 103, 116, 37, 155, 217, 4, 51, 4, 173, 250, 93, 62, 234, 98, 118, 11, 217, 190, 151, 99, 77, 99]);
|
||||||
|
const publicKeyTwo = hexToU8a('0x44a996beb1eef7bdcab976ab6d2ca26104834164ecf28fb375600576fcc6eb0f');
|
||||||
|
const seedOne = stringToU8a('12345678901234567890123456789012');
|
||||||
|
const seedTwo = hexToU8a('0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60');
|
||||||
|
let keypair: Keyring;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
keypair = new Keyring({ type: 'sr25519' });
|
||||||
|
|
||||||
|
keypair.addFromSeed(seedOne, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates with dev phrase when only path specified', () => {
|
||||||
|
expect(
|
||||||
|
keypair.createFromUri('//Alice').address()
|
||||||
|
).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds the pair', () => {
|
||||||
|
expect(
|
||||||
|
keypair.addFromSeed(seedTwo, {}).publicKey()
|
||||||
|
).toEqual(publicKeyTwo);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds from a mnemonic', () => {
|
||||||
|
setPrefix(68);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
keypair.addFromMnemonic('moral movie very draw assault whisper awful rebuild speed purity repeat card', {}).address()
|
||||||
|
).toEqual('7qQGarA4PWjPPVHG4USn1yuuVZvEHN7XZz8o7EbAp48jayZQ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows publicKeys retrieval', () => {
|
||||||
|
keypair.addFromSeed(seedTwo, {});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
keypair.getPublicKeys()
|
||||||
|
).toEqual([ publicKeyOne, publicKeyTwo ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows retrieval of a specific item', () => {
|
||||||
|
expect(
|
||||||
|
keypair.getPair(publicKeyOne).publicKey()
|
||||||
|
).toEqual(publicKeyOne);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows adding from JSON', () => {
|
||||||
|
expect(
|
||||||
|
keypair.addFromJson(
|
||||||
|
JSON.parse('{"address":"5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua","encoded":"0xb4a14995d25ab609f3686e9fa45f1fb237cd833f33f00d4b12c51858ca070d96972e47d73aae5eeb0fc06f923826cf0943fdb02c2c2ee30ef52a7912663053940d1da4da66b3a3f520ae07422c1c94b2d95690fca9d1f4a997623bb2923a8833280e19e7f72c3c5cfa343974e60e2b3dc53b404fdaf330756daad5e4e3","encoding":{"content":"pkcs8","type":"xsalsa20-poly1305","version":"0"},"meta":{"isTesting":true,"name":"alice"}}')
|
||||||
|
).publicKey()
|
||||||
|
).toEqual(
|
||||||
|
new Uint8Array([209, 114, 167, 76, 218, 76, 134, 89, 18, 195, 43, 160, 168, 10, 87, 174, 105, 171, 174, 65, 14, 92, 203, 89, 222, 232, 78, 47, 68, 50, 219, 79])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('signs and verifies', () => {
|
||||||
|
const MESSAGE = stringToU8a('this is a message');
|
||||||
|
const pair = keypair.getPair(publicKeyOne);
|
||||||
|
const signature = pair.sign(MESSAGE);
|
||||||
|
|
||||||
|
expect(pair.verify(MESSAGE, signature)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
// Copyright 2017-2019 @polkadot/keyring authors & contributors
|
||||||
|
// This software may be modified and distributed under the terms
|
||||||
|
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import { KeypairType } from '@plugnet/util-crypto/types';
|
||||||
|
import { KeyringInstance, KeyringPair, KeyringPair$Json, KeyringPair$Meta, KeyringOptions } from './types';
|
||||||
|
|
||||||
|
import { assert, assertSingletonPackage, hexToU8a, isNumber, isHex, stringToU8a } from '@plugnet/util';
|
||||||
|
import { keyExtractSuri, naclKeypairFromSeed as naclFromSeed, schnorrkelKeypairFromSeed as schnorrkelFromSeed, mnemonicToMiniSecret, keyFromPath } from '@plugnet/util-crypto';
|
||||||
|
|
||||||
|
import { decodeAddress, encodeAddress, setAddressPrefix } from './address';
|
||||||
|
import { DEV_PHRASE } from './defaults';
|
||||||
|
import createPair from './pair';
|
||||||
|
import Pairs from './pairs';
|
||||||
|
|
||||||
|
assertSingletonPackage('@plugnet/keyring');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* # @plugnet/keyring
|
||||||
|
*
|
||||||
|
* ## Overview
|
||||||
|
*
|
||||||
|
* @name Keyring
|
||||||
|
* @summary Keyring management of user accounts
|
||||||
|
* @description Allows generation of keyring pairs from a variety of input combinations, such as
|
||||||
|
* json object containing account address or public key, account metadata, and account encoded using
|
||||||
|
* `addFromJson`, or by providing those values as arguments separately to `addFromAddress`,
|
||||||
|
* or by providing the mnemonic (seed phrase) and account metadata as arguments to `addFromMnemonic`.
|
||||||
|
* Stores the keyring pairs in a keyring pair dictionary. Removal of the keyring pairs from the keyring pair
|
||||||
|
* dictionary is achieved using `removePair`. Retrieval of all the stored pairs via `getPairs` or perform
|
||||||
|
* lookup of a pair for a given account address or public key using `getPair`. JSON metadata associated with
|
||||||
|
* an account may be obtained using `toJson` accompanied by the account passphrase.
|
||||||
|
*/
|
||||||
|
export default class Keyring implements KeyringInstance {
|
||||||
|
private _pairs: Pairs;
|
||||||
|
private _type: KeypairType;
|
||||||
|
|
||||||
|
constructor (options: KeyringOptions = {}) {
|
||||||
|
options.type = options.type || 'ed25519';
|
||||||
|
|
||||||
|
assert(options && ['ed25519', 'sr25519'].includes(options.type || 'undefined'), `Expected a keyring type of either 'ed25519' or 'sr25519', found '${options.type}`);
|
||||||
|
|
||||||
|
this._pairs = new Pairs();
|
||||||
|
this._type = options.type;
|
||||||
|
|
||||||
|
setAddressPrefix(isNumber(options.addressPrefix) ? options.addressPrefix : 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeAddress = decodeAddress;
|
||||||
|
encodeAddress = encodeAddress;
|
||||||
|
setAddressPrefix = setAddressPrefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description retrieve the pairs (alias for getPairs)
|
||||||
|
*/
|
||||||
|
get pairs (): Array<KeyringPair> {
|
||||||
|
return this.getPairs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description retrieve the publicKeys (alias for getPiblicKeys)
|
||||||
|
*/
|
||||||
|
get publicKeys (): Array<Uint8Array> {
|
||||||
|
return this.getPublicKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Returns the type of the keyring, either ed25519 of sr25519
|
||||||
|
*/
|
||||||
|
get type (): KeypairType {
|
||||||
|
return this._type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name addPair
|
||||||
|
* @summary Stores an account, given a keyring pair, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
||||||
|
*/
|
||||||
|
addPair (pair: KeyringPair): KeyringPair {
|
||||||
|
return this._pairs.add(pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name addFromAddress
|
||||||
|
* @summary Stores an account, given an account address, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
||||||
|
* @description Allows user to explicitely provide separate inputs including account address or public key, and optionally
|
||||||
|
* the associated account metadata, and the default encoded value as arguments (that may be obtained from the json file
|
||||||
|
* of an account backup), and then generates a keyring pair from them that it passes to
|
||||||
|
* `addPair` to stores in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
|
||||||
|
*/
|
||||||
|
addFromAddress (address: string | Uint8Array, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null, type: KeypairType = this.type, ignoreChecksum?: boolean): KeyringPair {
|
||||||
|
const publicKey = this.decodeAddress(address, ignoreChecksum);
|
||||||
|
|
||||||
|
return this.addPair(createPair(type, { publicKey }, meta, encoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name addFromJson
|
||||||
|
* @summary Stores an account, given JSON data, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
||||||
|
* @description Allows user to provide a json object argument that contains account information (that may be obtained from the json file
|
||||||
|
* of an account backup), and then generates a keyring pair from it that it passes to
|
||||||
|
* `addPair` to stores in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
|
||||||
|
*/
|
||||||
|
addFromJson ({ address, encoded, encoding: { content, version }, meta }: KeyringPair$Json, ignoreChecksum?: boolean): KeyringPair {
|
||||||
|
const type = version === '0' || !Array.isArray(content)
|
||||||
|
? this.type
|
||||||
|
: content[1];
|
||||||
|
|
||||||
|
return this.addFromAddress(address, meta, hexToU8a(encoded), type, ignoreChecksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name addFromMnemonic
|
||||||
|
* @summary Stores an account, given a mnemonic, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
||||||
|
* @description Allows user to provide a mnemonic (seed phrase that is provided when account is originally created)
|
||||||
|
* argument and a metadata argument that contains account information (that may be obtained from the json file
|
||||||
|
* of an account backup), and then generates a keyring pair from it that it passes to
|
||||||
|
* `addPair` to stores in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
|
||||||
|
*/
|
||||||
|
addFromMnemonic (mnemonic: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
|
||||||
|
return this.addFromUri(mnemonic, meta, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name addFromSeed
|
||||||
|
* @summary Stores an account, given seed data, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
||||||
|
* @description Stores in a keyring pair dictionary the public key of the pair as a key and the pair as the associated value.
|
||||||
|
* Allows user to provide the account seed as an argument, and then generates a keyring pair from it that it passes to
|
||||||
|
* `addPair` to store in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
|
||||||
|
*/
|
||||||
|
addFromSeed (seed: Uint8Array, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
|
||||||
|
const keypair = type === 'sr25519'
|
||||||
|
? schnorrkelFromSeed(seed)
|
||||||
|
: naclFromSeed(seed);
|
||||||
|
|
||||||
|
return this.addPair(createPair(type, keypair, meta, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name addFromUri
|
||||||
|
* @summary Creates an account via an suri
|
||||||
|
* @description Extracts the phrase, path and password from a SURI format for specifying secret keys `<secret>/<soft-key>//<hard-key>///<password>` (the `///password` may be omitted, and `/<soft-key>` and `//<hard-key>` maybe repeated and mixed). The secret can be a hex string, mnemonic phrase or a string (to be padded)
|
||||||
|
*/
|
||||||
|
addFromUri (suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
|
||||||
|
return this.addPair(
|
||||||
|
this.createFromUri(suri, meta, type)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name createFromUri
|
||||||
|
* @summry Creates a Keypair from an suri
|
||||||
|
* @description This creates a pair from the suri, but does not add it to the keyring
|
||||||
|
*/
|
||||||
|
createFromUri (_suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
|
||||||
|
// here we only aut-add the dev phrase if we have a hard-derived path
|
||||||
|
const suri = _suri.indexOf('//') === 0
|
||||||
|
? `${DEV_PHRASE}${_suri}`
|
||||||
|
: _suri;
|
||||||
|
const { password, phrase, path } = keyExtractSuri(suri);
|
||||||
|
let seed;
|
||||||
|
|
||||||
|
if (isHex(phrase, 256)) {
|
||||||
|
seed = hexToU8a(phrase);
|
||||||
|
} else {
|
||||||
|
const str = phrase as string;
|
||||||
|
const parts = str.split(' ');
|
||||||
|
|
||||||
|
if ([12, 15, 18, 21, 24].includes(parts.length)) {
|
||||||
|
// FIXME This keeps compat with older versions, but breaks compat with subkey
|
||||||
|
// seed = type === 'sr25519'
|
||||||
|
// ? mnemonicToMiniSecret(phrase, password)
|
||||||
|
// : mnemonicToSeed(phrase, password);
|
||||||
|
seed = mnemonicToMiniSecret(phrase, password);
|
||||||
|
} else {
|
||||||
|
assert(str.length <= 32, 'specified phrase is not a valid mnemonic and is invalid as a raw seed at > 32 bytes');
|
||||||
|
|
||||||
|
seed = stringToU8a(str.padEnd(32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const keypair = type === 'sr25519'
|
||||||
|
? schnorrkelFromSeed(seed)
|
||||||
|
: naclFromSeed(seed);
|
||||||
|
const derived = keyFromPath(keypair, path, type);
|
||||||
|
|
||||||
|
return createPair(type, derived, meta, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name getPair
|
||||||
|
* @summary Retrieves an account keyring pair from the Keyring Pair Dictionary, given an account address
|
||||||
|
* @description Returns a keyring pair value from the keyring pair dictionary by performing
|
||||||
|
* a key lookup using the provided account address or public key (after decoding it).
|
||||||
|
*/
|
||||||
|
getPair (address: string | Uint8Array): KeyringPair {
|
||||||
|
return this._pairs.get(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name getPairs
|
||||||
|
* @summary Retrieves all account keyring pairs from the Keyring Pair Dictionary
|
||||||
|
* @description Returns an array list of all the keyring pair values that are stored in the keyring pair dictionary.
|
||||||
|
*/
|
||||||
|
getPairs (): Array<KeyringPair> {
|
||||||
|
return this._pairs.all();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name getPublicKeys
|
||||||
|
* @summary Retrieves Public Keys of all Keyring Pairs stored in the Keyring Pair Dictionary
|
||||||
|
* @description Returns an array list of all the public keys associated with each of the keyring pair values that are stored in the keyring pair dictionary.
|
||||||
|
*/
|
||||||
|
getPublicKeys (): Array<Uint8Array> {
|
||||||
|
return this._pairs
|
||||||
|
.all()
|
||||||
|
.map(({ publicKey }) =>
|
||||||
|
publicKey()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name removePair
|
||||||
|
* @description Deletes the provided input address or public key from the stored Keyring Pair Dictionary.
|
||||||
|
*/
|
||||||
|
removePair (address: string | Uint8Array): void {
|
||||||
|
this._pairs.remove(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name toJson
|
||||||
|
* @summary Returns a JSON object associated with the input argument that contains metadata assocated with an account
|
||||||
|
* @description Returns a JSON object containing the metadata associated with an account
|
||||||
|
* when valid address or public key and when the account passphrase is provided if the account secret
|
||||||
|
* is not already unlocked and available in memory. Note that in [Polkadot-JS Apps](https://github.com/polkadot-js/apps) the user
|
||||||
|
* may backup their account to a JSON file that contains this information.
|
||||||
|
*/
|
||||||
|
toJson (address: string | Uint8Array, passphrase?: string): KeyringPair$Json {
|
||||||
|
return this._pairs.get(address).toJson(passphrase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Keyring, decodeAddress, encodeAddress, setAddressPrefix };
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user