Commit 787e3388 by cf

first commit

parents
{
"rules": {
"no-bitwise": "off",
"semi": ["error", "always"],
"indent": ["error", 4]
},
"parserOptions": {
"requireConfigFile": false
},
"overrides": [
{
"files": ["**/*.ts", "**/*.tsx"],
"rules":{
"indent": "off",
"@typescript-eslint/indent": ["error", 4],
"@typescript-eslint/no-var-requires": "off",
"semi": "off",
"@typescript-eslint/semi": ["error", "always"]
}
}
]
}
---
name: Bug report
about: Create a report to help us improve
---
**Version of ZSWJS**
_which version of zswjs exhibits the issue_
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
<!-- PLEASE FILL OUT THE FOLLOWING MARKDOWN TEMPLATE -->
<!-- PR title alone should be sufficient to understand changes. -->
## Change Description
<!-- Describe your changes, their justification, AND their impact. Reference issues or pull requests where possible (use '#XX' or 'GH-XX' where XX is the issue or pull request number). -->
## API Changes
- [ ] API Changes
<!-- checked [x] = API changes; unchecked [ ] = no changes, ignore this section -->
<!-- If this PR introduces API changes, please describe the changes here. What will developers need to know before upgrading to this version? -->
## Documentation Additions
- [ ] Documentation Additions
<!-- checked [x] = Documentation changes; unchecked [ ] = no changes, ignore this section -->
<!-- Describe what must be added to the documentation after merge. -->
.DS_Store
.idea
.vscode
dist/
dist-web/
node_modules/
docs-build/
*.tgz
.github/**/*.wasm
.github/**/*.abi
#cypress artifacts
cypress/screenshots/
cypress/vidzsw/
# Exclude all files by default
*
# Include distribution bundle but exclude test files if they are built into dist
!dist/**
*.test.*
# Include documentation and version information in bundle
!CONTRIBUTING.md
# Include any additional source files which should be bundled
!src/**/*.abi.json
//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}
\ No newline at end of file
# Contributing to ZSWJS
Interested in contributing? That's awesome! Here are some guidelines to get started quickly and easily:
- [Reporting An Issue](#reporting-an-issue)
- [Bug Reports](#bug-reports)
- [Feature Requests](#feature-requests)
- [Change Requests](#change-requests)
- [Working on ZSWJS](#working-on-zswjs)
- [Feature Branches](#feature-branches)
- [Submitting Pull Requests](#submitting-pull-requests)
- [Testing](#testing)
- [Quality Assurance](#quality-assurance)
- [Conduct](#conduct)
- [Contributor License & Acknowledgments](#contributor-license--acknowledgments)
- [References](#references)
## Reporting An Issue
If you're about to raise an issue because you think you've found a problem with ZSWJS, or you'd like to make a request for a new feature in the codebase, or any other reason… please read this first.
The GitHub issue tracker is the preferred channel for [bug reports](#bug-reports), [feature requests](#feature-requests), and [submitting pull requests](#submitting-pull-requests), but please respect the following restrictions:
* Please **search for existing issues**. Help us keep duplicate issues to a minimum by checking to see if someone has already reported your problem or requested your idea.
* Please **be civil**. Keep the discussion on topic and respect the opinions of others. See also our [Contributor Code of Conduct](#conduct).
### Bug Reports
A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are extremely helpful - thank you!
Guidelines for bug reports:
1. **Use the GitHub issue search** &mdash; check if the issue has already been
reported.
1. **Check if the issue has been fixed** &mdash; look for [closed issues in the
current milestone](https://github.com/zhongshuwen/zswjs/issues?q=is%3Aissue+is%3Aclosed) or try to reproduce it
using the latest `develop` branch.
A good bug report shouldn't leave others needing to chase you up for more information. Be sure to include the details of your environment and relevant tests that demonstrate the failure.
[Report a bug](https://github.com/zhongshuwen/zswjs/issues/new?template=bug_report.md)
### Feature Requests
Feature requests are welcome. Before you submit one be sure to have:
1. **Use the GitHub search** and check the feature hasn't already been requested.
1. Take a moment to think about whether your idea fits with the scope and aims of the project.
1. Remember, it's up to *you* to make a strong case to convince the project's leaders of the merits of this feature. Please provide as much detail and context as possible, this means explaining the use case and why it is likely to be common.
### Change Requests
Change requests cover both architectural and functional changes to how ZSWJS works. If you have an idea for a new or different dependency, a refactor, or an improvement to a feature, etc - please be sure to:
1. **Use the GitHub search** and check someone else didn't get there first
1. Take a moment to think about the best way to make a case for, and explain what you're thinking. Are you sure this shouldn't really be
a [bug report](#bug-reports) or a [feature request](#feature-requests)? Is it really one idea or is it many? What's the context? What problem are you solving? Why is what you are suggesting better than what's already there?
## Working on ZSWJS
Code contributions are welcome and encouraged! If you are looking for a good place to start, check out the [good first issue](https://github.com/zhongshuwen/zswjs/labels/good%20first%20issue) label in GitHub issues.
Also, please follow these guidelines when submitting code:
### Feature Branches
To get it out of the way:
- **[develop](https://github.com/zhongshuwen/zswjs/tree/develop)** is the development branch. All work on the next release happens here so you should generally branch off `develop`. Do **NOT** use this branch for a production site.
- **[master](https://github.com/zhongshuwen/zswjs/tree/master)** contains the latest release of ZSWJS. This branch may be used in production. Do **NOT** use this branch to work on ZSWJS's source.
### Submitting Pull Requests
Pull requests are awesome. If you're looking to raise a PR for something which doesn't have an open issue, please think carefully about [raising an issue](#reporting-an-issue) which your PR can close, especially if you're fixing a bug. This makes it more likely that there will be enough information available for your PR to be properly tested and merged.
### Testing
ZSWJS is used by many libraries across the 中数文联盟链 ecosystem, so proper testing is absolutely essential prior to opening a pull request. This can be done in ZSWJS by running `yarn build-production`. This command will build the distrubution bundles (`yarn build-all`) and test each environment accordingly (`yarn test-all`).
#### Automated Unit Test Suite
`yarn test` will run through the core functionality of each ZSWJS module with Jest.
#### Integration Test Suite
Integration tests will only work with a local node running on port 8888 and with test accounts "bob" and "alice".
##### Web Environment
Run `yarn build-web` to create the `dist-web` folder and web distrubution modules then `yarn test-web`. This will run through the `tests/web.html` file using Cypress to inform you on the command line of any test failures.
##### NodeJS Environment
Run `yarn build` to build the NPM distribution bundle then run `yarn test-node`. This will create an out of box node environment with `tests/node.js` then test that environment with Jest and relay the results to the command line.
### Quality Assurance
Never underestimate just how useful quality assurance is. If you're looking to get involved with the code base and don't know where to start, checking out and testing a pull request is one of the most useful things you could do.
Essentially, [check out the latest develop branch](#working-on-zswjs), take it for a spin, and if you find anything odd, please follow the [bug report guidelines](#bug-reports) and let us know!
## Conduct
While contributing, please be respectful and constructive, so that participation in our project is a positive experience for everyone.
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others’ private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Contributor License & Acknowledgments
Whenever you make a contribution to this project, you license your contribution under the same terms as set out in LICENSE, and you represent and warrant that you have the right to license your contribution under those terms. Whenever you make a contribution to this project, you also certify in the terms of the Developer’s Certificate of Origin set out below:
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
## References
* Overall CONTRIB adapted from https://github.com/mathjax/MathJax/blob/master/CONTRIBUTING.md
* Conduct section adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
MIT License
Copyright (c) 2022 中数文科技有限公司
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-----------------------------------------------
Crypto-js derived code is Copyright (c) 2009-2013, Jeff Mott
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-----------------------------------------------
The MIT License (MIT)
Triplesec derived code is Copyright (c) 2013, Maxwell Krohn
browserify-aes contributions are Copyright (c) 2014-2017 browserify-aes contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-----------------------------------------------
Copyright (c) 2017-2020 block.one and its contributors. All rights reserved.
The MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# zswjs
[![Build Status](https://github.com/zswchain/zswjs/workflows/CI/badge.svg?branch=master)](https://github.com/zhongshuwen/zswjs/actions) [![npm version](https://badge.fury.io/js/zswjs.svg)](https://badge.fury.io/js/zswjs) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ![npm](https://img.shields.io/npm/dw/zswjs.svg)
Documentation can be found [here](https://zswchain.github.io/zswjs)
## Installation
### NPM
The official distribution package can be found at [npm](https://www.npmjs.com/package/zswjs).
### Add dependency to your project
`yarn add zswjs`
### Using with Typescript
In order to get access to the `TextEncoding` and `TextDecoding` types, you need to add `@types/text-encoding` as a dev dependency:
`yarn add --dev @types/text-encoding`
If you're using Node (not a browser) then you'll also need to make sure the `dom` lib is referenced in your `tsconfig.json`:
```
{
"compilerOptions": {
"lib": [..., "dom"]
}
}
```
### Browser Distribution
Clone this repository locally then run `yarn build-web`. The browser distribution will be located in `dist-web` and can be directly copied into your project repository. The `dist-web` folder contains minified bundles ready for production, along with source mapped versions of the library for debugging. For full browser usage examples, [see the documentation](https://zswchain.github.io/zswjs/guides/1.-Browsers.html).
## Import
### ES Modules
Importing using ESM syntax is supported using TypeScript, [webpack](https://webpack.js.org/api/module-methods), or [Node.js with `--experimental-modules` flag](https://nodejs.org/api/esm.html)
```js
import { Api, JsonRpc, RpcError } from 'zswjs';
import { JsSignatureProvider } from 'zswjs/dist/zswjs-jssig'; // development only
```
### CommonJS
Importing using commonJS syntax is supported by Node.js out of the box.
```js
const { Api, JsonRpc, RpcError } = require('zswjs');
const { JsSignatureProvider } = require('zswjs/dist/zswjs-jssig'); // development only
const fetch = require('node-fetch'); // node only; not needed in browsers
const { TextEncoder, TextDecoder } = require('util'); // node only; native TextEncoder/Decoder
```
## Basic Usage
### Signature Provider
The Signature Provider holds private keys and is responsible for signing transactions.
***Using the JsSignatureProvider in the browser is not secure and should only be used for development purposes. Use a secure vault outside of the context of the webpage to ensure security when signing transactions in production***
```js
const defaultPrivateKey = "5JtUScZK2XEp3g9gh7F8bwtPTRAkASmNrrftmx4AxDKD5K4zDnr"; // bob
const signatureProvider = new JsSignatureProvider([defaultPrivateKey]);
```
### JSON-RPC
Open a connection to JSON-RPC, include `fetch` when on Node.js.
```js
const rpc = new JsonRpc('http://127.0.0.1:8888', { fetch });
```
### API
Include textDecoder and textEncoder when using in Node. You may exclude these when running in a browser since most modern browsers now natively support these. If your browser does not support these (https://caniuse.com/#feat=textencoder), then you can import them as a dependency through the following deprecated npm package: https://www.npmjs.com/package/text-encoding
```js
const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), textEncoder: new TextEncoder() });
```
### Sending a transaction
`transact()` is used to sign and push transactions onto the blockchain with an optional configuration object parameter. This parameter can override the default value of `broadcast: true`, and can be used to fill TAPOS fields given `expireSeconds` and either `blocksBehind` or `useLastIrreversible`. Given no configuration options, transactions are expected to be unpacked with TAPOS fields (`expiration`, `ref_block_num`, `ref_block_prefix`) and will automatically be broadcast onto the chain.
```js
(async () => {
const result = await api.transact({
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
from: 'useraaaaaaaa',
to: 'useraaaaaaab',
quantity: '0.0001 SYS',
memo: '',
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
console.dir(result);
})();
```
### Error handling
use `RpcError` for handling RPC Errors
```js
...
try {
const result = await api.transact({
...
} catch (e) {
console.log('\nCaught exception: ' + e);
if (e instanceof RpcError)
console.log(JSON.stringify(e.json, null, 2));
}
...
```
## Contributing
[Contributing Guide](./CONTRIBUTING.md)
[Code of Conduct](./CONTRIBUTING.md#conduct)
## License
[MIT](./LICENSE)
## Important
See [LICENSE](./LICENSE) for copyright and license terms.
All repositories and other materials are provided subject to the terms of this [IMPORTANT](./IMPORTANT.md) notice and you must familiarize yourself with its terms. The notice contains important information, limitations and restrictions relating to our software, publications, trademarks, third-party resources, and forward-looking statements. By accessing any of our repositories and other materials, you accept and agree to the terms of the notice.
{
"video": true,
"fixturesFolder": false,
"pluginsFile": false,
"supportFile": false,
"userAgent": "Chrome cypress"
}
import { skipOn } from '@cypress/skip-test';
describe('zswjs web test', () => {
it('loads', () => {
cy.visit(('./src/tests/web.html'));
});
it('test Transact With Config Blocks Behind', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testTransactWithConfigBlocksBehind').click();
cy.get('#testTransactWithConfigBlocksBehind').contains('Success', { timeout: 5000 });
});
it('test Transact With Config Use Last Irreversible', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testTransactWithConfigUseLastIrreversible').click();
cy.get('#testTransactWithConfigUseLastIrreversible').contains('Success', { timeout: 5000 });
});
it('test Transact Without Config', () => {
if (Cypress.env('NODZSW_VER')) skipOn(Cypress.env('NODZSW_VER') === 'release/2.0.x');
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testTransactWithoutConfig').click();
cy.get('#testTransactWithoutConfig').contains('Success', { timeout: 5000 });
});
it('test Transact With Compression', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testTransactWithCompression').click();
cy.get('#testTransactWithCompression').contains('Success', { timeout: 5000 });
});
it('test Transact With Context Free Action', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testTransactWithContextFreeAction').click();
cy.get('#testTransactWithContextFreeAction').contains('Success', { timeout: 5000 });
});
it('test Transact With Context Free Data', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testTransactWithContextFreeData').click();
cy.get('#testTransactWithContextFreeData').contains('Success', { timeout: 5000 });
});
it('test Transact Without Broadcast', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testTransactWithoutBroadcast').click();
cy.get('#testTransactWithoutBroadcast').contains('Success', { timeout: 5000 });
});
it('test Broadcast Result', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testBroadcastResult').click();
cy.get('#testBroadcastResult').contains('Success', { timeout: 5000 });
});
it('test Shorthand With Api Json', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testShorthandWithApiJson').click();
cy.get('#testShorthandWithApiJson').contains('Success', { timeout: 5000 });
});
it('test Shorthand With Tx Json', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testShorthandWithTxJson').click();
cy.get('#testShorthandWithTxJson').contains('Success', { timeout: 5000 });
});
it('test Shorthand With Tx Json Context Free Action', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testShorthandWithTxJsonContextFreeAction').click();
cy.get('#testShorthandWithTxJsonContextFreeAction').contains('Success', { timeout: 5000 });
});
it('test Shorthand With Tx Json Context Free Data', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testShorthandWithTxJsonContextFreeData').click();
cy.get('#testShorthandWithTxJsonContextFreeData').contains('Success', { timeout: 5000 });
});
it('test With P256 Elliptic Curve', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testWithP256EllipticCurve').click();
cy.get('#testWithP256EllipticCurve').contains('Success', { timeout: 5000 });
});
it('test With Return Value Tx', () => {
if (Cypress.env('NODZSW_VER')) skipOn(Cypress.env('NODZSW_VER') === 'release/2.0.x');
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testWithReturnValueTx').click();
cy.get('#testWithReturnValueTx').contains('Success', { timeout: 5000 });
});
it('test With Resource Payer Tx', () => {
if (Cypress.env('NODZSW_VER')) skipOn(Cypress.env('NODZSW_VER') === 'release/2.0.x' || Cypress.env('NODZSW_VER') === 'release/2.1.x');
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testWithResourcePayerTx').click();
cy.get('#testWithResourcePayerTx').contains('Success', { timeout: 5000 });
});
it('test With Read Only Query', () => {
if (Cypress.env('NODZSW_VER')) skipOn(Cypress.env('NODZSW_VER') === 'release/2.0.x' || Cypress.env('NODZSW_VER') === 'release/2.1.x');
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testWithReadOnlyQuery').click();
cy.get('#testWithReadOnlyQuery').contains('Success', { timeout: 5000 });
});
it('test With Read Only Failure Trace', () => {
if (Cypress.env('NODZSW_VER')) skipOn(Cypress.env('NODZSW_VER') === 'release/2.0.x' || Cypress.env('NODZSW_VER') === 'release/2.1.x');
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testWithReadOnlyFailureTrace').click();
cy.get('#testWithReadOnlyFailureTrace').contains('Success', { timeout: 5000 });
});
it('test Transact Should Fail', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testTransactShouldFail').click();
cy.get('#testTransactShouldFail').contains('Success', { timeout: 5000 });
});
it('test Rpc Should Fail', () => {
cy.visit(('./src/tests/web.html'));
cy.wait(500);
cy.get('#testRpcShouldFail').click();
cy.get('#testRpcShouldFail').contains('Success', { timeout: 5000 });
});
});
{
"name": "zswjs",
"generators": [
{
"name": "collate_markdown",
"options": {
"docs_dir": "docs"
}
},
{
"name": "typedoc",
"options": {
"disable_default_filters": true,
"filters": [
{ "name": "remove_extension" }
]
}
}
]
}
As stated in the [introduction](index.md), `zswjs` integrates with 中数文联盟链-based blockchains using the [中数文联盟链 Nodzsw RPC API](https://chaindocs.zhongshuwen.com).
In general, there are two objects that are used to interact with a blockchain via `zswjs`: the `JsonRpc` object, and the `Api` object.
## JsonRpc
The `JsonRpc` object is typically used when signing is not necessary. Some examples include [getting block information](how-to-guides/00_how-to-get-block-information.md), [getting transaction information](how-to-guides/02_how-to-get-transaction-information.md), or [getting table information](how-to-guides/09_how-to-get-table-information.md).
The requests made by the `JsonRpc` object will either use a built-in `fetch` library, or [the `fetch` library passed in by the user](basic-usage/01_commonjs.md) to issue requests to the endpoint specified when instantiating the `JsonRpc` object. When the various methods ([get_abi](https://github.com/zhongshuwen/zswjs/blob/master/src/zswjs-jsonrpc.ts#L66), [get_account](https://github.com/zhongshuwen/zswjs/blob/master/src/zswjs-jsonrpc.ts#L71), [get_block_header_state](https://github.com/zhongshuwen/zswjs/blob/master/src/zswjs-jsonrpc.ts#L76), etc) of the `JsonRpc` object are invoked, the calls are delegated to the `JsonRpc` object's [fetch function](https://github.com/zhongshuwen/zswjs/blob/master/src/zswjs-jsonrpc.ts#L42-L63), which in turn, delegate the requests to the `fetch` library.
## Api
The `Api` object is typically used when transacting on an 中数文联盟链-based blockchain. Some examples include [staking](how-to-guides/03_how-to-stake.md), [creating an account](how-to-guides/05_how-to-create-an-account.md), or [proposing multi-sig transactions](how-to-guides/13_how-to-propose-a-multisig-transaction.md).
The typical use of the `Api` object is to call its [`transact` method](https://github.com/zhongshuwen/zswjs/blob/master/src/zswjs-api.ts#L214-L254). This method performs a number of steps depending on the input passed to it:
* The `transact` method first checks if the **chainId** was set in the `Api` constructor, and if not, uses the [`JsonRpc` object's](#jsonrpc) [`get_info`](https://github.com/zhongshuwen/zswjs/blob/master/src/zswjs-jsonrpc.ts#L101) method to retrieve the **chainId**.
* The `transact` method then checks if the `expireSeconds` and either `blocksBehind` or `useLastIrreversible` fields are set and well-formed in the [optional configuration object, as specified in *How to Submit a Transaction*](how-to-guides/01_how-to-submit-a-transaction.md#).
* If so, either the *last_irreversible_block_num* or the block *blocksBehind* the head block retrieved from [`JsonRpc`'s `get_info`](https://github.com/zhongshuwen/zswjs/blob/master/src/zswjs-jsonrpc.ts#L101) is set as the reference block and the transaction header is serialized using this reference block and the `expireSeconds` field.
* The `transact` method then checks if the appropriate TAPOS fields are present in the transaction ([they can either be specified directly in the transaction or in the optional configuration object](how-to-guides/01_how-to-submit-a-transaction.md#)) and throws an Error if not.
* The necessary `abi`s for a transaction are then retrieved for the case when `transact` is expected to sign the transaction.
* The `actions` are serialized using the `zswjs-serialize` `ser` object.
* The entire transaction is then [serialized](https://github.com/zhongshuwen/zswjs/blob/master/src/zswjs-api.ts#L154-L166), also using the `zswjs-serialize` `ser` object.
* The transaction is then optionally signed, using the `signatureProvider`, the previously retrieved `abi`s, the private keys of the `signatureProvider`, and the `chainId`.
* The transaction is then optionally compressed, using the `deflate` function of a Javascript zlib library.
* The transaction is then optionally broadcasted using `JsonRpc`'s [`push_transaction`](https://github.com/zhongshuwen/zswjs/blob/master/src/zswjs-jsonrpc.ts#L187).
\ No newline at end of file
`zswjs` can be installed via [`yarn`](https://yarnpkg.com/en/)
```javascript
yarn add zswjs
```
or [`npm`](https://www.npmjs.com/)
```javascript
npm install zswjs
```
\ No newline at end of file
To use `zswjs` in a browser run `npm run build-web` or `yarn build-web`. This will create the `dist-web` folder and web distribution modules. Ensure that you include `externals.min.js` as it includes external packages that zswjs uses.
```html
<pre style="width: 100%; height: 100%; margin:0px; "></pre>
<script src='dist-web/externals.min.js'></script>
<script src='dist-web/zswjs-api.min.js'></script>
<script src='dist-web/zswjs-jsonrpc.min.js'></script>
<script src='dist-web/zswjs-jssig.min.js'></script>
```
To cache ABIs and reduce network usage, reuse the `api` object for all transactions. This implies you should only call `new zswjs_api.Api(...)` once.
```html
<script>
let pre = document.getElementsByTagName('pre')[0];
const defaultPrivateKey = "5JtUScZK2XEp3g9gh7F8bwtPTRAkASmNrrftmx4AxDKD5K4zDnr"; // bob
const rpc = new zswjs_jsonrpc.JsonRpc('http://localhost:8888');
const signatureProvider = new zswjs_jssig.JsSignatureProvider([defaultPrivateKey]);
const api = new zswjs_api.Api({ rpc, signatureProvider });
(async () => {
try {
const result = await api.transact({
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'bob',
permission: 'active',
}],
data: {
from: 'bob',
to: 'alice',
quantity: '0.0001 SYS',
memo: '',
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
pre.textContent += '\n\nTransaction pushed!\n\n' + JSON.stringify(result, null, 2);
} catch (e) {
pre.textContent = '\nCaught exception: ' + e;
if (e instanceof zswjs_jsonrpc.RpcError)
pre.textContent += '\n\n' + JSON.stringify(e.json, null, 2);
}
})();
</script>
```
## Debugging
If you would like readable source files for debugging, change the file reference to the `.js` files inside `dist-web` directory. These files should only be used for development as they are over 10 times as large as the minified versions, and importing the debug versions will increase loading times for the end user.
## IE11 and Edge Support
If you need to support IE11 or Edge you will also need to install a text-encoding polyfill, as zswjs Signing is dependent on the TextEncoder which IE11 and Edge do not provide. Pass the TextEncoder and TextDecoder to the API constructor as demonstrated in the [CommonJS example](01_commonjs.md). Refer to the documentation [here](https://github.com/inexorabletash/text-encoding) to determine the best way to include it in your project.
\ No newline at end of file
To import `zswjs` using commonjs syntax follow the code below.
```javascript
const { Api, JsonRpc } = require('zswjs');
const { JsSignatureProvider } = require('zswjs/dist/zswjs-jssig'); // development only
const fetch = require('node-fetch'); //node only
const { TextDecoder, TextEncoder } = require('util'); //node only
const privateKeys = [privateKey1];
const signatureProvider = new JsSignatureProvider(privateKeys);
const rpc = new JsonRpc('http://127.0.0.1:8888', { fetch }); //required to read blockchain state
const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), textEncoder: new TextEncoder() }); //required to submit transactions
```
\ No newline at end of file
To import `zswjs` using [ES module syntax](https://en.wikipedia.org/wiki/ECMAScript) the following code is provided.
```javascript
import { Api, JsonRpc } from 'zswjs';
import { JsSignatureProvider } from 'zswjs/dist/zswjs-jssig'; // development only
const privateKeys = [privateKey1];
const signatureProvider = new JsSignatureProvider(privateKeys);
const rpc = new JsonRpc('http://127.0.0.1:8888'); //required to read blockchain state
const api = new Api({ rpc, signatureProvider }); //required to submit transactions
```
\ No newline at end of file
The `zswjs` package provides two objects: an `Api` object and a `JsonRpc` object. An explanation of their expected parameters and usage is provided below.
## JsonRpc
The `JsonRpc` object takes the node you wish to connect to in the form of a string as a required constructor argument, as well as an optional `fetch` object (see [CommonJS](01_commonjs.md) for an example).
Note that reading blockchain state requires only an instance of `JsonRpc` connected to a node and not the `Api` object.
## Api
To send transactions and trigger actions on the blockchain, you must have an instance of `Api`. This `Api` instance is required to receive a SignatureProvider object in it's constructor.
The SignatureProvider object must contain the private keys corresponding to the actors and permission requirements of the actions being executed.
## JsSignatureProvider
The Api constructor requires a SignatureProvider. SignatureProviders implement the `dist/zswjs-api-interfaces.SignatureProvider` interface. For development purpose only, a `JsSignatureProvider` object is also provided via the `dist/zswjs-jssig` import to stand-in for an easy option for a signature provider during development, but should only be used in development, as it is not secure.
In production code, it is recommended that you use a secure vault outside of the context of the webpage (which will also implement the `zswjs-api-interfaces.SignatureProvider` interface) to ensure security when signing transactions.
`zswjs` provides an example implementation of the [`SignatureProvider` interface](https://github.com/zhongshuwen/zswjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/zswjs-api-interfaces.ts#L60) called [`JsSignatureProvider`](https://github.com/zhongshuwen/zswjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/zswjs-jssig.ts#L11).
Although `JsSignatureProvider` is insecure and should not be used in production, it provides a basic example of what a `SignatureProvider` is and does. `JsSignatureProvider` simply takes a list of private keys as strings in its constructor and maps these private keys to their respective public keys. When [`JsSignatureProvider`'s `sign`](https://github.com/zhongshuwen/zswjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/zswjs-jssig.ts#L33) is called, a buffer of the `chainId` and `serializedTransaction` is created and [`zsw-crypto`'s `ecc` object](https://github.com/中数文联盟链/zsw-crypto/blob/7ec577cad54e17da6168fdfb11ec2b09d6f0e7f0/src/index.js#L4) is used to sign the buffer with the private key corresponding to the required public key.
\ No newline at end of file
Since it is not recommended that you use `JsSignatureProvider`, a list of `SignatureProvider`s is provided below, along with a link to the documentation:
* [Ledger Signature Provider](https://github.com/zhongshuwen/zswjs-ledger-signature-provider)
\ No newline at end of file
To get block information call `get_block` on the rpc object passing in the block number as a required argument.
```javascript
(async () => {
await rpc.get_block(1) //get the first block
})();
```
The block data is returned as JSON.
```json
{
"timestamp": "2018-06-01T12:00:00.000",
"producer": "",
"confirmed": 1,
"previous": "0000000000000000000000000000000000000000000000000000000000000000",
"transaction_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
"action_mroot": "cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f",
"schedule_version": 0,
"new_producers": null,
"header_extensions": [],
"producer_signature": "SIG_K1_111111111111111111111111111111111111111111111111111111111111111116uk5ne",
"transactions": [],
"block_extensions": [],
"id": "00000001bcf2f448225d099685f14da76803028926af04d2607eafcf609c265c",
"block_num": 1,
"ref_block_prefix": 2517196066
}
```
\ No newline at end of file
To submit a transaction, call `transact` on the api object, passing in two parameters.
The first parameter specifies the actions in the transaction, and their corresponding authorizations, as well as any data necessary for the action to execute. An example for the [`buyrambytes` action](https://github.com/zhongshuwen/zswchain)) is shown below.
```javascript
{
actions: [{
account: 'zswchain',
name: 'buyrambytes',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
payer: 'useraaaaaaaa',
receiver: 'useraaaaaaaa',
bytes: 8192,
},
}]
}
```
The second parameter is an [optional configuration object parameter](https://github.com/zhongshuwen/zswjs/blob/master/src/zswjs-api.ts#L215). This optional parameter can override the default values of `broadcast: true` and `sign: true`, and can be used to fill [TAPOS](https://zswchain.stackexchange.com/questions/2362/what-is-transaction-as-proof-of-stake-tapos-and-when-would-a-smart-contract) fields with the specified `expireSeconds` and either `blocksBehind` or `useLastIrreversible` if necessary. A combination of these fields are required if the first parameter specified above does not itself contain the TAPOS fields `expiration`, `ref_block_num`, and `ref_block_prefix`. In this case it does not, so the fields are necessary.
```javascript
{
blocksBehind: 3,
expireSeconds: 30,
}
```
Below is a complete example transaction to call the `buyrambytes` action with `useraaaaaaaa`'s active permission, `useraaaaaaaa` is also both the payer and receiver of **8192** bytes of RAM.
The transaction will reference the block 3 blocks behind the head block, and will automatically expire the transaction 30 seconds after the time present in this referenced block.
```javascript
(async () => {
const transaction = await api.transact({
actions: [{
account: 'zswchain',
name: 'buyrambytes',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
payer: 'useraaaaaaaa',
receiver: 'useraaaaaaaa',
bytes: 8192,
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
})();
```
Alternatively, the transaction could be submitted without the optional configuration object by specifying the TAPOS fields `expiration`, `ref_block_num`, and `ref_block_prefix` explicity in the action.
```javascript
(async () => {
const transaction = await api.transact({
expiration: '2019-09-19T16:39:15',
ref_block_num: '50477227',
ref_block_prefix: '1022379673',
actions: [{
account: 'zswchain',
name: 'buyrambytes',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
payer: 'useraaaaaaaa',
receiver: 'useraaaaaaaa',
bytes: 8192,
},
}]
});
})();
```
#### Concise Actions
To construct transactions and actions in a more concise way, you can also utilize this format instead:
```javascript
(async () => {
await api.transact({
actions: [
api.with('zswchain').as('useraaaaaaaa').buyrambytes('useraaaaaaaa', 'useraaaaaaaa', 8192)
]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
})();
```
With this concise format of an action, the `with()` function has the account, `as()` contains the actor, and the name of the action is the third function. The arguments within the action function are listed in the same order as the arguments from the smart contract. You can also send a longer authentication within the `as()` function, such as `[{ actor: ‘useraaaaaaaa’, permission: ‘active’}]`.
Before using this structure, you need to cache the JSON Abi:
```javascript
(async () => {
await api.getAbi('zswchain');
...
})();
```
Additionally, utilizing this structure, a stateful transaction object can be created and passed through your application before sending when ready. The transaction object can also be created as a callback method.
```javascript
(async () => {
const tx = api.buildTransaction();
tx.with('zswchain').as('useraaaaaaaa').buyrambytes('useraaaaaaaa', 'useraaaaaaaa', 8192)
await tx.send({ blocksBehind: 3, expireSeconds: 30 });
// ...or...
api.buildTransaction(async (tx) => {
tx.with('zswchain').as('useraaaaaaaa').buyrambytes('useraaaaaaaa', 'useraaaaaaaa', 8192)
await tx.send({ blocksBehind: 3, expireSeconds: 30 });
});
})();
```
By using this object and passing it around your application, it might be more difficult for your application to keep the correct references and indexes for context free actions. The transaction object has a function for mapping actions, context free actions, and context free data together.
```javascript
(async () => {
const tx = api.buildTransaction();
tx.associateContextFree((index) => ({
contextFreeData: cfdata,
contextFreeAction: tx.with('account').as().cfaName(index.cfd, 'context free example'),
action: tx.with('account').as('actor').actionName('example', index.cfa)
}));
await tx.send({ blocksBehind: 3, expireSeconds: 30 });
})();
```
By providing that function inside `tx.associateContextFree()`, the transaction object will provide the correct indexes for the context free action and context free data. You can input the `index.cfa` or `index.cfd` arguments where your smart contract requires that index in the list of arguments. Additionally, all three object keys are not necessary in the function, in case for example, the action is not necessary for your context free action.
#### Return Values
From nodzsw version 2.1, the ability to receive return values from smart contracts to zswjs has been introduced. In the above examples, the `transaction` object will include the values `transaction_id` and the `processed` object. If your smart contract returns values, you will be able to find the values within the `transaction.processed.action_traces` array. The order of the `action_traces` array matches the order of actions in your transaction and within those `action_trace` objects, you can find your deserialized return value for your action in the `return_value` field.
### Read-Only Transactions
From nodzsw version 2.2, read-only queries have been introduced to zswjs. Adding `readOnlyTrx` to the `transact` config will send the transaction through the `push_ro_transaction` endpoint in the `chain_api`. The `push_ro_transaction` endpoint does not allow the transaction to make any data changes despite the actions in the transaction. The `push_ro_transaction` endpoint may also be used to call normal actions, but any data changes that action will make will be rolled back.
Adding returnFailureTraces to the transact config enables the return of a trace message if your transaction fails. At this time, this is only available for the `push_ro_transaction` endpoint.
**Note** that [`history_get_transaction`](https://github.com/zhongshuwen/zswjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/zswjs-jsonrpc.ts#L205) below uses the deprecated `/v1/history/get_transaction` endpoint of a node.
To get a transaction's information, call [`history_get_transaction`](https://github.com/zhongshuwen/zswjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/zswjs-jsonrpc.ts#L205) on the rpc object passing in the transaction's id and optionally, it's block number as arguments.
```javascript
(async () => {
await rpc.history_get_transaction('b3598da4e007173e6d1b94d7be306299dd0a6813d114cf9a08c8e88a5756f1eb', 46632826)
})();
```
The transaction info is returned as JSON.
```javascript
{
id: 'b3598da4e007173e6d1b94d7be306299dd0a6813d114cf9a08c8e88a5756f1eb',
trx: {
receipt: {
status: 'executed',
cpu_usage_us: 2070,
net_usage_words: 14,
trx: [Array]
},
trx: {
expiration: '2019-08-28T03:45:47',
ref_block_num: 36720,
ref_block_prefix: 654845510,
max_net_usage_words: 0,
max_cpu_usage_ms: 0,
delay_sec: 0,
context_free_actions: [],
actions: [Array],
transaction_extensions: [],
signatures: [Array],
context_free_data: []
}
},
block_time: '2019-08-28T03:45:21.500',
block_num: 46632826,
last_irreversible_block: 46784285,
traces: []
}
```
\ No newline at end of file
To stake resources, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`delegatebw`](https://github.com/zhongshuwen/zswchain)) action of the `zswchain` account.
In the example shown below `useraaaaaaaa` stakes **1.0000 SYS** of NET and CPU to the account `mynewaccount`.
```javascript
(async () => {
await api.transact({
actions: [{
account: 'zswchain',
name: 'delegatebw',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
from: 'useraaaaaaaa',
receiver: 'mynewaccount',
stake_net_quantity: '1.0000 SYS',
stake_cpu_quantity: '1.0000 SYS',
transfer: false,
}
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
})();
```
\ No newline at end of file
To unstake resources, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`undelegatebw`](https://github.com/zhongshuwen/zswchain)) action of the `zswchain` account.
In the example shown below `useraaaaaaaa` unstakes **1.0000 SYS** of NET and CPU from the account `mynewaccount`.
```javascript
(async () => {
await api.transact({
actions: [{
account: 'zswchain',
name: 'undelegatebw',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
from: 'useraaaaaaaa',
receiver: 'mynewaccount',
stake_net_quantity: '1.0000 SYS',
stake_cpu_quantity: '1.0000 SYS',
transfer: false,
}
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
})();
```
\ No newline at end of file
To create a new account submit three actions to the `zswchain` account using the `actions` array as shown in [how-to-submit-a-transaction](01_how-to-submit-a-transaction.md).
## newaccount
The first action is the [`newaccount`](https://github.com/zhongshuwen/zswchain)) action. In the example shown below `useraaaaaaaa` creates new account `mynewaccount` with owner and active public key `PUB_R1_6FPFZqw5ahYrR9jD96yDbbDNTdKtNqRbze6oTDLntrsANgQKZu`. Ideally, these should be different public keys.
```javascript
{
account: 'zswchain',
name: 'newaccount',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
creator: 'useraaaaaaaa',
name: 'mynewaccount',
owner: {
threshold: 1,
keys: [{
key: 'PUB_R1_6FPFZqw5ahYrR9jD96yDbbDNTdKtNqRbze6oTDLntrsANgQKZu',
weight: 1
}],
accounts: [],
waits: []
},
active: {
threshold: 1,
keys: [{
key: 'PUB_R1_6FPFZqw5ahYrR9jD96yDbbDNTdKtNqRbze6oTDLntrsANgQKZu',
weight: 1
}],
accounts: [],
waits: []
},
}
}
```
## buyrambytes
The second action is the [`buyrambytes`](https://github.com/zhongshuwen/zswchain)) action. In the example shown below `useraaaaaaaa` pays for **8192** bytes of RAM for the account `mynewaccount` created in the [first action](#newaccount).
```javascript
{
account: 'zswchain',
name: 'buyrambytes',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
payer: 'useraaaaaaaa',
receiver: 'mynewaccount',
bytes: 8192,
},
}
```
## delegatebw
The third action is the [`delegatebw`](https://github.com/zhongshuwen/zswchain)) action. In the example shown below `useraaaaaaaa` delegates **1.0000 SYS** of NET and CPU to the account `mynewaccount` created in the [first action](#newaccount).
```javascript
{
account: 'zswchain',
name: 'delegatebw',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
from: 'useraaaaaaaa',
receiver: 'mynewaccount',
stake_net_quantity: '1.0000 SYS',
stake_cpu_quantity: '1.0000 SYS',
transfer: false,
}
}
```
## Create An Account
Below the three actions are submitted as one transaction using the `Api` object.
```javascript
(async () => {
await api.transact({
actions: [{
account: 'zswchain',
name: 'newaccount',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
creator: 'useraaaaaaaa',
name: 'mynewaccount',
owner: {
threshold: 1,
keys: [{
key: 'PUB_R1_6FPFZqw5ahYrR9jD96yDbbDNTdKtNqRbze6oTDLntrsANgQKZu',
weight: 1
}],
accounts: [],
waits: []
},
active: {
threshold: 1,
keys: [{
key: 'PUB_R1_6FPFZqw5ahYrR9jD96yDbbDNTdKtNqRbze6oTDLntrsANgQKZu',
weight: 1
}],
accounts: [],
waits: []
},
},
},
{
account: 'zswchain',
name: 'buyrambytes',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
payer: 'useraaaaaaaa',
receiver: 'mynewaccount',
bytes: 8192,
},
},
{
account: 'zswchain',
name: 'delegatebw',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
from: 'useraaaaaaaa',
receiver: 'mynewaccount',
stake_net_quantity: '1.0000 SYS',
stake_cpu_quantity: '1.0000 SYS',
transfer: false,
}
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
})();
```
\ No newline at end of file
In order to deploy a smart contract using `zswjs`, call the [`setcode`](https://github.com/zhongshuwen/zswchain)) followed by the [`setabi`](https://github.com/zhongshuwen/zswchain)) actions of the `zswchain` account.
## setcode
`setcode` takes the name of the account where the smart contract will be deployed to and the smart contract **.wasm** file. The smart contract **.wasm** file should be a hex string. Assuming that a valid **.wasm** file is located at `/mypath/my_smart_contract.wasm`, converting a smart contract to a hex string can be accomplished using the code below.
```javascript
const fs = require('fs')
const wasmFilePath = '/mypath/my_smart_contract.wasm'
const wasmHexString = fs.readFileSync(wasmFilePath).toString('hex')
```
In the example shown below `useraaaaaaaa` sets the account `useraaaaaaaa`'s code to the smart contract located at `/mypath/my_smart_contract.wasm`'s hex string representation.
```javascript
{
account: 'zswchain',
name: 'setcode',
authorization: [
{
actor: 'useraaaaaaaa',
permission: 'active',
},
],
data: {
account: 'useraaaaaaaa',
code: wasmHexString,
},
}
```
## setabi
`setabi` takes the name of the account where the smart contract will be deployed to and the serialized **.abi** file corresponding to the **.wasm** used in the [`setcode`](#setcode) action corresponding to this `setabi` action. The following code is provided to serialize **.abi** files.
```javascript
const fs = require('fs')
const buffer = new Serialize.SerialBuffer({
textEncoder: api.textEncoder,
textDecoder: api.textDecoder,
})
const abiFilePath = '/mypath/my_smart_contract.abi'
let abiJSON = JSON.parse(fs.readFileSync(abiFilePath, 'utf8'))
const abiDefinitions = api.abiTypes.get('abi_def')
abiJSON = abiDefinitions.fields.reduce(
(acc, { name: fieldName }) =>
Object.assign(acc, { [fieldName]: acc[fieldName] || [] }),
abiJSON
)
abiDefinitions.serialize(buffer, abiJSON)
serializedAbiHexString = Buffer.from(buffer.asUint8Array()).toString('hex')
```
Note that the `api` object from [initialization](../basic-usage/01_commonjs.md) is used for it's `textEncoder`and `textDecoder` objects, as well as it's [`abiTypes`](https://github.com/zhongshuwen/zswjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/zswjs-api.ts#L72) map.
This line in particular:
```javascript
abiJSON = abiDefinitions.fields.reduce(
(acc, { name: fieldName }) =>
Object.assign(acc, { [fieldName]: acc[fieldName] || [] }),
abiJSON
)
```
ensures that the **.abi** file contains [the fields that an **.abi** file is expected to contain](https://github.com/zhongshuwen/zswjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/abi.abi.json#L151). Note that if an expected field is missing, the call to `serialize` will [throw an exception](https://github.com/zhongshuwen/zswjs/blob/849c03992e6ce3cb4b6a11bf18ab17b62136e5c9/src/zswjs-serialize.ts#L644) indicating the missing field.
## Deploying a Smart Contract
Below the two actions are submitted as one transaction using the `Api` object.
```javascript
(async () => {
await api.transact({
actions: [
{
account: 'zswchain',
name: 'setcode',
authorization: [
{
actor: 'useraaaaaaaa',
permission: 'active',
},
],
data: {
account: 'useraaaaaaaa',
code: wasmHexString,
},
},
{
account: 'zswchain',
name: 'setabi',
authorization: [
{
actor: 'useraaaaaaaa',
permission: 'active',
},
],
data: {
account: 'useraaaaaaaa',
abi: serializedAbiHexString,
},
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
});
})();
```
The entire code is provided below for reference.
```javascript
const wasmFilePath = '/mypath/my_smart_contract.wasm'
const abiFilePath = '/mypath/my_smart_contract.abi'
const wasmHexString = fs.readFileSync(wasmFilePath).toString('hex')
const buffer = new Serialize.SerialBuffer({
textEncoder: api.textEncoder,
textDecoder: api.textDecoder,
})
let abiJSON = JSON.parse(fs.readFileSync(abiFilePath, 'utf8'))
const abiDefinitions = api.abiTypes.get('abi_def')
abiJSON = abiDefinitions.fields.reduce(
(acc, { name: fieldName }) =>
Object.assign(acc, { [fieldName]: acc[fieldName] || [] }),
abiJSON
)
abiDefinitions.serialize(buffer, abiJSON)
serializedAbiHexString = Buffer.from(buffer.asUint8Array()).toString('hex')
await api.transact(
{
actions: [
{
account: 'zswchain',
name: 'setcode',
authorization: [
{
actor: 'useraaaaaaaa',
permission: 'active',
},
],
data: {
account: 'useraaaaaaaa',
code: wasmHexString,
},
},
{
account: 'zswchain',
name: 'setabi',
authorization: [
{
actor: 'useraaaaaaaa',
permission: 'active',
},
],
data: {
account: 'useraaaaaaaa',
abi: serializedAbiHexString,
},
},
],
},
{
blocksBehind: 3,
expireSeconds: 30,
}
)
```
\ No newline at end of file
To get a specific account's information call `get_account` on the rpc object passing in the account name as a function argument.
```javascript
(async () => {
await rpc.get_account('alice') //get alice's account info. This assumes the account 'alice' has been created on the chain specified in the rpc object.
})();
```
The account info is returned as JSON.
```json
{ "account_name": "testacc",
"head_block_num": 1079,
"head_block_time": "2018-11-10T00:45:53.500",
"privileged": false,
"last_code_update": "1970-01-01T00:00:00.000",
"created": "2018-11-10T00:37:05.000",
"ram_quota": -1,
"net_weight": -1,
"cpu_weight": -1,
"net_limit": { "used": -1, "available": -1, "max": -1 },
"cpu_limit": { "used": -1, "available": -1, "max": -1 },
"ram_usage": 2724,
"permissions":
[ { "perm_name": "active", "parent": "owner", "required_auth": [] },
{ "perm_name": "owner", "parent": "", "required_auth": [] } ],
"total_resources": null,
"self_delegated_bandwidth": null,
"refund_request": null,
"voter_info": null }
```
\ No newline at end of file
To transfer an zswchain token, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`transfer`](https://github.com/zhongshuwen/zswchain)) action of the account storing the token you wish to transfer.
In the example shown below `useraaaaaaaa` transfers **1.0000 ZSW** token stored in the `zsw.token` account from `useraaaaaaaa` to `userbbbbbbbb`.
```javascript
(async () => {
await api.transact({
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
from: 'useraaaaaaaa',
to: 'userbbbbbbbb',
quantity: '1.0000 ZSW',
memo: 'some memo'
}
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
})();
```
\ No newline at end of file
There are many ways to retrieve data stored in smart contract tables. A few are provided below.
## Get Table Rows
In the example shown below, the `zsw.token` smart contract's table `accounts` is queried with the scope `testacc`. The data is returned as **json**, in-order, and limited to **10 rows**. The RAM payer for the returned row is also not shown.
```javascript
(async () => {
await rpc.get_table_rows({
json: true, // Get the response as json
code: 'zsw.token', // Contract that we target
scope: 'testacc', // Account that owns the data
table: 'accounts', // Table name
limit: 10, // Maximum number of rows that we want to get
reverse: false, // Optional: Get reversed data
show_payer: false // Optional: Show ram payer
});
})();
```
Above we console log the response from the 中数文联盟链 network. An example of an expected response is shown below.
```javascript
{
rows: [ { balance: '68.3081 ZSW' }, { balance: '200.0000 JUNGLE' } ],
more: false
}
```
Note that since `more: false` was returned, if can be inferred that there are only 2 rows with scope `testacc` in the `accounts` table of the `zsw.token` smart contract.
## Get Currency Balance
Rather than using the `get_table_rows` method, a token balance can also be retrieved using the `get_currency_balance` method. This method takes an `account` which is a smart contract storing the tokens, an `account` who has a balance in the token table of the specified smart contract, and the `symbol` of the token to retrieve the currency balance for.
In the example shown below, the balance of the user `testacc`'s tokens with the symbol `ZSW` stored in the `zsw.token` smart contract is retrieved.
```javascript
(async () => {
console.log(await rpc.get_currency_balance('zsw.token', 'testacc', 'ZSW'));
})();
```
Above we console log the response from the 中数文联盟链 network. An example of an expected response is shown below.
```javascript
[ '68.3081 ZSW' ]
```
## Query By Index
A `lower_bound` parameter can also be passed to the `get_table_rows` method. This parameter allows you to query for a particular value of the primary key in the table. Using this in conjunction with `limit: 1` allows you to query for 1 row of a table.
In the example shown below, the `contract` smart contract's table `profiles` is queried with the scope `contract` for the row with primary key `testacc`. The `limit` is **1** which implies that only 1 row with value `testacc` will be returned.
```javascript
(async () => {
console.log(await rpc.get_table_rows({
json: true, // Get the response as json
code: 'contract', // Contract that we target
scope: 'contract', // Account that owns the data
table: 'profiles', // Table name
lower_bound: 'testacc', // Table primary key value
limit: 1, // Here we limit to 1 to get only the single row with primary key equal to 'testacc'
reverse: false, // Optional: Get reversed data
show_payer: false, // Optional: Show ram payer
}));
})();
```
Above we console log the response from the 中数文联盟链 network. An example of an expected response is shown below.
```javascript
{
"rows": [{
"user": "testacc",
"age": 21,
"surname": "Martin"
}
],
"more": false
}
```
## Query By Secondary Index
Finally, the `lower_bound` parameter can be used in conjunction with the `index_position` parameter to query an index different from the primary key.
In the example shown below, the `contract` smart contract's table `profiles` is queried with the scope `contract` for the rows with secondary index `age` equal to **21**. The `limit` is **1** which implies that only 1 row with the age **21** will be returned.
```javascript
(async () => {
console.log(await rpc.get_table_rows({
json: true, // Get the response as json
code: 'contract', // Contract that we target
scope: 'contract', // Account that owns the data
table: 'profiles', // Table name
index_position: 2, // Table secondary index
lower_bound: 21, // Table secondary key value
limit: 1, // Here we limit to 1 to get only row
reverse: false, // Optional: Get reversed data
show_payer: false, // Optional: Show ram payer
}));
})();
```
## Query Data using the Key-Value API (KV API)
The KV API is a new api which allows smart contract developers to create datastore key value tables on-chain. KV tables can have multiple indices, unique indices and non-unique indices. The table must have at least one unique index. If the smart contract uses KV tables use the get_kv_table_rows RPC call to query data.
In the example shown below, the `contract` smart contract's kv table `profiles` is queried via the index named `users` for the row with primary key `testacc`. The `limit` is **1** which implies that only 1 row with value `testacc` will be returned.
```javascript
(async () => {
console.log(await rpc.get_kv_table_rows({
json: false, // Get the response as json
code: 'contract', // Contract that we target
table: 'profiles', // Tablename
indexName: 'users', // The name of the index name
indexValue: 'testacc', // Table primary key value
limit: 1, // Here we limit to 1 to get only the single row with primary key equal to 'testacc'
reverse: false, // Optional: Get reversed data
show_payer: false, // Optional: Show ram payer
}));
})();
```
Above we console log the response from the 中数文联盟链 network. An example of an expected response is shown below.
```javascript
{
"rows": [{
"user": "testacc",
"age": 21,
"surname": "Martin"
}
],
"more": false
}
```
If the KV table has an additional indexes these can be used to query the data. The example shown below, is based on the previous example however in this case an index called `ages` is defined. This index is used to query the table for records where the persons age is 17.
```javascript
(async () => {
console.log(await rpc.get_kv_table_rows({
json: false, // Get the response as json
code: 'contract', // Contract that we target
table: 'profiles', // Tablename
indexName: 'ages', // The name of the index name
lowerBound: '17', // Table primary key value
upperBound: '17', // Table primary key value
limit: 1, // Here we limit to 1 to get only the single row with primary key equal to 'testacc'
reverse: false, // Optional: Get reversed data
show_payer: false, // Optional: Show ram payer
}));
})();
```
Above we console log the response from the 中数文联盟链 network. An example of an expected response is shown below.
```javascript
{
"rows": [{
"user": "otheracc",
"age": 17,
"surname": "Dubious"
}
],
"more": false
}
```
To create new permissions, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`updateauth`](https://github.com/zhongshuwen/zswchain)) action of the `zswchain` account.
In the example shown below `useraaaaaaaa` creates a new permission called `my_new_permission` on the account `useraaaaaaaa`, with the public key `PUB_R1_6FPFZqw5ahYrR9jD96yDbbDNTdKtNqRbze6oTDLntrsANgQKZu`.
```javascript
const authorization_object = {
threshold: 1,
accounts: [{
permission: {
actor: "useraaaaaaaa",
permission: "active"
},
weight: 1
}],
keys: [{
key: 'PUB_R1_6FPFZqw5ahYrR9jD96yDbbDNTdKtNqRbze6oTDLntrsANgQKZu',
weight: 1
}],
waits: []
};
const updateauth_input = {
account: 'useraaaaaaaa',
permission: 'my_new_permission',
parent: 'active',
auth: authorization_object
};
(async () => {
await api.transact({
actions: [
{
account: 'zswchain',
name: 'updateauth',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: updateauth_input,
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
})();
```
You can check that the new permission exists on the account using [`get_account`](07_how-to-get-account-information.md)
\ No newline at end of file
To delete permissions, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`deleteauth`](https://github.com/zhongshuwen/zswchain)) action of the `zswchain` account.
In the example shown below `useraaaaaaaa` deletes the permission `my_new_permission` on the account `useraaaaaaaa`.
```javascript
const deleteauth_input = {
account: 'useraaaaaaaa',
permission: 'my_new_permission',
};
(async () => {
await api.transact({
actions: [
{
account: 'zswchain',
name: 'deleteauth',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: delete_auth_data,
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
})();
```
\ No newline at end of file
To link an existing permission, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`linkauth`](https://github.com/zhongshuwen/zswchain)) action of the `zswchain` account.
In the example shown below `useraaaaaaaa` links the permission `action_perm` to the contract `useraaaaaaaa`'s `contract_action` action.
```javascript
const linkauth_input = {
account: 'useraaaaaaaa', // the permission's owner to be linked and the payer of the RAM needed to store this link
code: 'useraaaaaaaa', // the owner of the action to be linked
type: 'contract_action', // the action to be linked
requirement: 'action_perm', // the permission to be linked
};
(async () => {
await api.transact({
actions: [{
account: 'zswchain',
name: 'linkauth',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: linkauth_input,
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
}));
})();
```
\ No newline at end of file
To unlink an existing permission, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`unlinkauth`](https://github.com/zhongshuwen/zswchain)) action of the `zswchain` account.
In the example shown below `useraaaaaaaa` unlinks the permissions present on the contract `useraaaaaaaa`'s `contract_action` action.
```javascript
const unlinkauth_input = {
account: 'useraaaaaaaa', // the permission's owner to be linked and the payer of the RAM needed to store this link
code: 'useraaaaaaaa', // the owner of the action to be linked
type: 'contract_action' // the action to be linked
};
(async () => {
await api.transact({
actions: [{
account: 'zswchain',
name: 'unlinkauth',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: unlinkauth_input,
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
})();
```
\ No newline at end of file
To propose a transaction, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`propose`](https://github.com/zhongshuwen/zswchain)) action of the `zswchain.msig` account.
## Serializing Actions
The `data` field of the `propose` action has a `tx` field, which is a [`transaction`](https://github.com/zhongshuwen/zswchain)) type. This `transaction` type contains an [`action`](https://github.com/zhongshuwen/zswchain)) type, which contains [`bytes`](https://github.com/zhongshuwen/zswchain)) as it's `data` field. Because of this, we must first serialize a list of [`action`](https://github.com/zhongshuwen/zswchain)) objects.
## serializeActions
In the example shown below, a transaction for the `zswchain` `updateauth` action is serialized using the `api` object's `serializeActions` method.
```javascript
const actions = [
{
account: 'zswchain',
name: 'updateauth',
authorization: [
{
actor: 'useraaaaaaaa',
permission: 'active',
}
],
data: {
account: 'useraaaaaaaa',
permission: 'active',
parent: '',
auth: {
threshold: 1,
keys: [
{
key: 'PUB_R1_6FPFZqw5ahYrR9jD96yDbbDNTdKtNqRbze6oTDLntrsANgQKZu',
weight: 1
}
],
accounts:[],
waits:[]
}
}
}
];
(async () => {
const serialized_actions = await api.serializeActions(actions)
}
```
An example output of `serialized_actions` call made above is shown below.
```javascript
[
{
account: 'zswchain',
name: 'updateauth',
authorization: [ [Object] ],
data: 'F0F0C30F3FFCF0C300000000A8ED3232000000000000000001000000010003FD9ABF3D22615D5621BF74D2D0A652992DE1338E552AD85D5EAF1F39DCAADDB301000000'
}
]
```
## Propose Input
In the example shown below, the `serialized_actions` list created above is used in the `actions` field of the `proposeInput`'s `trx` field.
[Below](#propose) `useraaaaaaaa` proposes a multi-sig transaction, which calls the `updateauth` action of the `zswchain` account (see [`actions`](#serializeactions) above). This proposal is called `changeowner` and both `useraaaaaaaa` and `userbbbbbbbb` must sign the multi-sig transaction before `2019-09-14T16:39:15`.
```javascript
const proposeInput = {
proposer: 'useraaaaaaaa',
proposal_name: 'changeowner',
requested: [
{
actor: 'useraaaaaaaa',
permission: 'active'
},
{
actor: 'userbbbbbbbb',
permission: 'active'
}
],
trx: {
expiration: '2019-09-14T16:39:15',
ref_block_num: 0,
ref_block_prefix: 0,
max_net_usage_words: 0,
max_cpu_usage_ms: 0,
delay_sec: 0,
context_free_actions: [],
actions: serialized_actions,
transaction_extensions: []
}
};
```
## Propose
In the example below, a transaction is submitted to the `propose` action of the `zswchain.msig` contract using the `proposeInput` object created [above](#propose-input).
```javascript
await api.transact({
actions: [{
account: 'zswchain.msig',
name: 'propose',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: proposeInput,
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
broadcast: true,
sign: true
});
```
## Propose a Multi-sig Transaction
Below all three steps to propose a multi-sig transaction are provided.
```javascript
// CREATE ACTION TO PROPOSE
const actions = [
{
account: 'zswchain',
name: 'updateauth',
authorization: [
{
actor: 'useraaaaaaaa',
permission: 'active',
}
], data: {
account: 'useraaaaaaaa',
permission: 'active',
parent: '',
auth: {
threshold: 1,
keys: [
{
key: 'PUB_R1_6FPFZqw5ahYrR9jD96yDbbDNTdKtNqRbze6oTDLntrsANgQKZu',
weight: 1
}
],
accounts:[],
waits:[]
}
},
}
];
(async () => {
const serialized_actions = await api.serializeActions(actions)
// BUILD THE MULTISIG PROPOSE TRANSACTION
proposeInput = {
proposer: 'useraaaaaaaa',
proposal_name: 'changeowner',
requested: [
{
actor: 'useraaaaaaaa',
permission: 'active'
},
{
actor: 'userbbbbbbbb',
permission: 'active'
}
],
trx: {
expiration: '2019-09-16T16:39:15',
ref_block_num: 0,
ref_block_prefix: 0,
max_net_usage_words: 0,
max_cpu_usage_ms: 0,
delay_sec: 0,
context_free_actions: [],
actions: serialized_actions,
transaction_extensions: []
}
};
//PROPOSE THE TRANSACTION
const result = await api.transact({
actions: [{
account: 'zswchain.msig',
name: 'propose',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: proposeInput,
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
broadcast: true,
sign: true
});
})();
```
To approve a multi-sig transaction, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`approve`](https://github.com/ZSWIO/zswchain.contracts/blob/52fbd4ac7e6c38c558302c48d00469a4bed35f7c/contracts/zswchain.msig/include/zswchain.msig/zswchain.msig.hpp#L58) action of the `zswchain.msig` account.
In the example shown below `userbbbbbbbb` approves the `changeowner` proposal, previously proposed by `useraaaaaaaa` using `userbbbbbbbb`'s `active` permission.
```javascript
(async () => {
await api.transact({
actions: [{
account: 'zswchain.msig',
name: 'approve',
authorization: [{
actor: 'userbbbbbbbb',
permission: 'active',
}],
data: {
proposer: 'useraaaaaaaa',
proposal_name: 'changeowner',
level: {
actor: 'userbbbbbbbb',
permission: 'active',
}
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
broadcast: true,
sign: true
});
})();
```
\ No newline at end of file
To unapprove a multi-sig transaction, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`unapprove`](https://github.com/zhongshuwen/zswchain)) action of the `zswchain.msig` account.
In the example shown below `userbbbbbbbb` unapproves the `changeowner` proposal, previously proposed by `useraaaaaaaa` using `userbbbbbbbb`'s `active` permission.
```javascript
(async () => {
await api.transact({
actions: [{
account: 'zswchain.msig',
name: 'unapprove',
authorization: [{
actor: 'userbbbbbbbb',
permission: 'active',
}],
data: {
proposer: 'useraaaaaaaa',
proposal_name: 'changeowner',
level: {
actor: 'userbbbbbbbb',
permission: 'active',
}
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
broadcast: true,
sign: true
});
})();
```
\ No newline at end of file
To cancel a multi-sig transaction, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`cancel`](https://github.com/zhongshuwen/zswchain)) action of the `zswchain.msig` account.
In the example shown below `useraaaaaaaa` cancels the `changeowner` proposal, previously proposed by `useraaaaaaaa`.
```javascript
(async () => {
await api.transact({
actions: [{
account: 'zswchain.msig',
name: 'cancel',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
proposer: 'useraaaaaaaa',
proposal_name: 'changeowner',
canceler: 'useraaaaaaaa'
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
broadcast: true,
sign: true
});
})();
```
**Note** that if a previously proposed transaction has yet to expire, only the proposer of the transaction can cancel it.
\ No newline at end of file
If a multi-sig transaction has been approved by the appropriate parties prior to the proposed transaction's expiration timestamp, it can be executed.
To execute a multi-sig transaction, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`exec`](https://github.com/zhongshuwen/zswchain)) action of the `zswchain.msig` account.
In the example shown below `userbbbbbbbb` executes the `changeowner` proposal, previously proposed by `useraaaaaaaa`.
```javascript
(async () => {
await api.transact({
actions: [{
account: 'zswchain.msig',
name: 'exec',
authorization: [{
actor: 'userbbbbbbbb',
permission: 'active',
}],
data: {
proposer: 'useraaaaaaaa',
proposal_name: 'changeowner',
executer: 'userbbbbbbbb'
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
broadcast: true,
sign: true
});
})();
```
\ No newline at end of file
To vote for a block produder, [submit a transaction](01_how-to-submit-a-transaction.md) to the [`voteproducer`](https://github.com/zhongshuwen/zswchain)) action of the `zswchain` account.
In the example shown below `useraaaaaaaa` votes for producers `userbbbbbbbb` and `usercccccccc`.
```javascript
(async () => {
await api.transact({
actions: [{
account: 'zswchain',
name: 'voteproducer',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
voter: 'useraaaaaaaa',
proxy: '',
producers: ['userbbbbbbbb', 'usercccccccc']
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
broadcast: true,
sign: true
});
})();
```
`useraaaaaaaa` can also delegate their vote to a proxy. In the example shown below, `useraaaaaaaa` delegates their vote to the proxy `userbbbbbbbb`.
```javascript
(async () => {
await api.transact({
actions: [{
account: 'zswchain',
name: 'voteproducer',
authorization: [{
actor: 'useraaaaaaaa',
permission: 'active',
}],
data: {
voter: 'useraaaaaaaa',
proxy: 'userbbbbbbbb',
producers: []
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
broadcast: true,
sign: true
});
})();
```
**Note** that if the `proxy` field is used, the `producers` list must be empty, and vice verse, if the `producers` list is used, the `proxy` field must be an empty string.
\ No newline at end of file
To change the network used by `zswjs`, pass in the URL of a node connected to the network of interest to `JsonRpc`.
In the examples shown in the `basic-usage` section, it was assumed that a local node running on port `8888` was used.
```javascript
const rpc = new JsonRpc('http://127.0.0.1:8888');
```
However, the address and port of any node on any network can be used by passing in the URL of the node as a string to the `JsonRpc` object. Assuming there is a node running at the IP address `192.168.2.1` listening for requests on port `9999`, we could connect to this node and it's network with the following line of code.
```javascript
const rpc = new JsonRpc('http://192.168.2.1:9999');
```
\ No newline at end of file
After the release of v2.2 of nodzsw, the resource payer feature is available to sponsor the resources for a transaction. To set a separate payer for the resources for a transaction, add a `resource_payer` object to your transaction that specifies the `payer`, `max_net_bytes`, `max_cpu_us`, and `max_memory_bytes`. This functionality requires the `RESOURCE_PAYER` protocol feature to be enabled on the chain.
A typical use-case for this feature has a service or application pay for the resources of a transaction instead of their users. Since authorization is required for both the user in the transaction and the payer, a possible workflow would have the transaction signed by the user's wallet application and then also signed by the service/application before sent to nodzsw.
```javascript
{
resource_payer: {
payer: 'alice',
max_net_bytes: 4096,
max_cpu_us: 400,
max_memory_bytes: 0
},
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'bob',
permission: 'active',
}, {
actor: 'alice',
permission: 'active',
}],
data: {
from: 'bob',
to: 'alice',
quantity: '0.0001 SYS',
memo: 'resource payer',
},
}]
}
```
---
content_title: zswjs
---
`zswjs` is a Javascript library which provides an API for integrating with ZSW-based 联盟链。 The documentation for `zswjs` is structured in the following way:
* [Installation](02_installation.md) explains how to install `zswjs` using `npm` or `yarn`.
* [Basic Usage](basic-usage/) provides information related to importing `zswjs` in various Javascript environments. The [basic-usage](basic-usage/index.md) document specifically provides brief explanations of the components provided by `zswjs` as well as their typical use cases.
* [FAQ](faq/) provides answers to frequently asked questions surrounding the `zswjs` software.
* [How-To Guides](how-to-guides/) provides how-tos on everything from getting block information to proposing and signing multi-sig transactions.
* [Troubleshooting](troubleshooting/) provides possible exceptions encountered when developing with `zswjs` and their most common causes.
* [Technical Overview](01_technical-overview.md) provides a high-level overview of how `zswjs` works.
While troubleshooting network connectivity it is advisable to issue a `rpc.get_info()` request to rule out the possibility of `Api` object misconfiguration or authorization related problems. Given the `JsonRpc` object only requires a node URL, this approach isolates problems to simple node connectivity problems.
Below are a number of ways a `rpc.get_info()` request can fail and what they could potentially mean.
## invalid json response body
```javascript
(node:66636) UnhandledPromiseRejectionWarning: FetchError: invalid json response body at http://www.some-node-url.com/v1/chain/get_info reason: Unexpected token < in JSON at position 0
```
This typically means you have connected to a computer that is not running 中数文联盟链 software. For example, if you instantiate a `JsonRpc` object as follows:
```javascript
const rpc = new JsonRpc('http://some-node-url.com', { fetch });
```
You would see the exception above when issuing a `rpc.get_info()` request since the computer at `http://some-node-url.com` is not running 中数文联盟链 software.
## ETIMEDOUT
```javascript
(node:68313) UnhandledPromiseRejectionWarning: FetchError: request to http://some-node-url.com:8000/v1/chain/get_info failed, reason: connect ETIMEDOUT 53.140.50.180:8000
```
This typically implies you have connected to a node that *is* running 中数文联盟链 software, but have entered the incorrect port, or left off the port number altogether.
## Indefinite Hanging
If the `rpc.get_info()` request never returns, but also never throws an exception, it is likely that you've connected to a node running 中数文联盟链 software, but have misconfigured the protocol (http/https).
## Only absolute URLs are supported
```javascript
(node:72394) UnhandledPromiseRejectionWarning: TypeError: Only absolute URLs are supported
```
This typically implies you've entered an empty string as the first argument to `JsonRpc` as shown below.
```javascript
const rpc = new JsonRpc('', { fetch });
```
## Only HTTP(S) protocols are supported
```javascript
(node:72612) UnhandledPromiseRejectionWarning: TypeError: Only HTTP(S) protocols are supported
```
This typically implies you've left off the protocol from the absolute URL string (i.e. `127.0.0.1:8888` rather than `http://127.0.0.1:8888`).
## ENOTFOUND
```javascript
(node:72822) UnhandledPromiseRejectionWarning: FetchError: request to http://www.some-node-url.com:8888/v1/chain/get_info failed, reason: getaddrinfo ENOTFOUND www.some-node-url.com
```
This typically implies you've misconfigured the domain name of the absolute URL in some way. Adding `www.` erroneously or removing `.com` at the end of the domain name could be possible mistakes.
## f is not a function
```javascript
(node:74052) UnhandledPromiseRejectionWarning: TypeError: f is not a function
```
This typically implies the absolute URL string of the node was not passed to the `JsonRpc` object as show below
```javascript
const rpc = new JsonRpc({ fetch });
```
Below are a number of ways authorization related requests can fail and what the failure could potentially mean.
## transaction declares authority but does not have signatures for it.
```javascript
(node:99019) UnhandledPromiseRejectionWarning: Error: transaction declares authority '{"actor":"useraaaaaaaa","permission":"active"}', but does not have signatures for it.
```
This exception can occur for a number of different reasons.
It could be that the private key supplied to the signature provider is incorrect for the actor in which the transaction is being signed for. It could also be that the `actor` field of the `authorization` object is incorrect. Finally, it could be that the `permission` specified is incorrect or does not exist for the specified actor.
## Invalid checksum
```javascript
Error: Invalid checksum, 01b93f != 5df6e0e2
```
This typically implies the private key supplied to the `JsSignatureProvider` object is malformed or invalid.
## Cannot read property 'length' of undefined
```javascript
(node:97736) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'length' of undefined
```
This typically implies you have not supplied an `authorization` field in the `action` supplied to the `transact` method of the `api` object.
For example, the below request would cause the above exception, since the `authorization` field is not present.
```javascript
(async () => {
await api.transact({
actions: [{
account: 'zswchain',
name: 'buyrambytes',
data: {
payer: 'useraaaaaaaa',
receiver: 'useraaaaaaaa',
bytes: 8192,
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
});
})();
```
## missing permission_level
```javascript
(node:472) UnhandledPromiseRejectionWarning: Error: missing permission_level.permission (type=name)
```
This means that you have left either the `actor` or `permission` field off of the `authorization` object of an action.
When a call to the chain_api is performed and fails, it will result in an RPCError object being generated which contains information on why the transaction failed.
The RPCError object will contain a concise error message, for instance 'Invalid transaction'. However additional details can be found in the `details` field and the `json` field. The `json` field holds the complete json response from nodzsw. The `details` field specifically holds the error object in the `json` field. The data content of the `json` and `details` vary depending on the endpoint is used to call nodzsw. Use the `details` field to quickly find error information.
In the `details` and `json` examples below, you can see that the error message may not contain enough information to discern what caused the action to fail. The error message contains `zswchain_assert_message` assertion failure. Looking further at the details you can see an `overdrawn balance` message.
```javascript
RpcError: zswchain_assert_message assertion failure
at new RpcError (zswjs-rpcerror.ts:20:13)
at JsonRpc.<anonymous> (zswjs-jsonrpc.ts:90:23)
at step (zswjs-jsonrpc.js:37:23)
at Object.next (zswjs-jsonrpc.js:18:53)
at fulfilled (zswjs-jsonrpc.js:9:58)
at processTicksAndRejections (node:internal/process/task_queues:94:5) {
details: {
code: 3050003,
name: 'zswchain_assert_message_exception',
message: 'zswchain_assert_message assertion failure',
stack: [
{
context: {
level: 'error',
file: 'cf_system.cpp',
line: 14,
method: 'zswchain_assert',
hostname: '',
thread_name: 'nodzsw',
timestamp: '2021-06-16T05:26:03.665'
},
format: 'assertion failure with message: ${s}',
data: { s: 'overdrawn balance' }
},
{
context: {
level: 'warn',
file: 'apply_context.cpp',
line: 143,
method: 'exec_one',
hostname: '',
thread_name: 'nodzsw',
timestamp: '2021-06-16T05:26:03.665'
},
format: 'pending console output: ${console}',
data: { console: '' }
}
]
},
json: {
head_block_num: 1079,
head_block_id: '00003384ff2dd671472e8290e7ee0fbc00ee1f450ce5c10de0a9c245ab5b5b22',
last_irreversible_block_num: 1070,
last_irreversible_block_id: '00003383946519b67bac1a0f31898826b472d81fd40b7fccb49a2f486bd292d1',
code_hash: '800bb7fedd86155047064bffdaa3c32cca76cda40eb80f5c4a7676c7f57da579',
pending_transactions: [],
result: {
id: '01a0cbb6c0215df53f07ecdcf0fb750a4134938b38a72946a0f6f25cf3f43bcb',
block_num: 1079,
block_time: '2021-06-14T21:13:04.500',
producer_block_id: null,
receipt: null,
elapsed: 189,
net_usage: 137,
scheduled: false,
action_traces: [Array],
account_ram_delta: null,
except: [Object],
error_code: '10000000000000000000',
bill_to_accounts: []
}
},
isFetchError: true
}
```
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "zswjs",
"version": "1.0.0",
"description": "中数文API",
"main": "dist/index.js",
"scripts": {
"cypress": "cypress run --spec 'cypress/integration/index.spec.js'",
"cypress-ui": "cypress open",
"prepare": "npm run build",
"lint": "eslint --ext .js,.jsx,.ts,.tsx src",
"test": "jest src/tests/*zswjs*",
"test-node": "jest src/tests/*node*",
"test-types": "jest src/tests/type-checks.test.ts",
"test-all": "yarn test && yarn test-node && yarn test-types",
"build": "rimraf dist && tsc -p ./tsconfig.json && node scripts/copy-ripe-md.js",
"build-web": "webpack --config webpack.prod.js && webpack --config webpack.debug.js",
"build-production": "yarn build && yarn build-web && yarn test-all",
"docs-init": "sh .docs/scripts/init.sh",
"docs-build": "sh .docs/scripts/build.sh",
"docs-serve": "python -m SimpleHTTPServer",
"docs-publish": "sh .docs/scripts/publish.sh"
},
"author": "中数文",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/zhongshuwen/zswjs.git"
},
"dependencies": {
"bn.js": "5.2.0",
"elliptic": "6.5.4",
"hash.js": "1.1.7",
"pako": "2.0.3"
},
"devDependencies": {
"@cypress/skip-test": "^2.6.1",
"@types/elliptic": "^6.4.13",
"@types/jest": "^26.0.24",
"@types/node": "^14.17.5",
"@types/node-fetch": "^2.5.11",
"@types/pako": "^1.0.2",
"buffer": "^6.0.3",
"clean-webpack-plugin": "^3.0.0",
"crypto-browserify": "^3.12.0",
"cypress": "^7.7.0",
"zsw-crypto": "^1.0.0",
"eslint": "^7.30.0",
"jest": "^26.6.3",
"jest-extended": "^0.11.5",
"jest-fetch-mock": "^3.0.3",
"rimraf": "^3.0.2",
"ts-jest": "^26.5.6",
"ts-loader": "^9.2.3",
"typescript": "^4.3.5",
"webpack": "^5.44.0",
"webpack-cli": "^4.7.2"
},
"jest": {
"automock": false,
"setupFiles": [
"./src/tests/setupJest.js"
],
"setupFilesAfterEnv": [
"jest-extended"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"^.+\\.(tsx?)$": "ts-jest"
},
"globals": {
"ts-jest": {
"tsconfig": "tsconfig.json"
}
},
"testRegex": "(/src/.*(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"testEnvironment": "node"
}
}
var fs = require('fs');
var path = require('path');
var root = __dirname.replace('scripts', '');
if(!fs.existsSync(path.join(root + 'dist')))
fs.mkdirSync(path.join(root + 'dist'));
fs.copyFileSync(path.join(root + 'src/ripemd.es5.js'), path.join(root + 'dist/ripemd.js'));
\ No newline at end of file
is_latest=false;
current_commit="$(git rev-parse HEAD)";
tags="$(git tag --sort=-creatordate)";
IFS='\n' read -ra arry <<< "$tags"
latest_tag="${arry[0]}"
if [ "$latest_tag" == "" ]; then
latest_tag="v0.0.0";
else
tag_commit="$(git rev-list -n 1 ${latest_tag})";
if [ "$tag_commit" == "$current_commit" ]; then
is_latest=true;
fi
fi
echo "tag_commit: ${tag_commit}";
echo "current_commit: ${current_commit}";
echo "is_latest: ${is_latest}";
export TRAVIS_IS_LATEST_TAG="$is_latest"
\ No newline at end of file
#!/bin/bash
. "${TRAVIS_BUILD_DIR}/scripts/publish-utils.sh";
echo "Running on branch/tag ${TRAVIS_BRANCH}":
echo "Setting up git"
setup_git
echo "Creating new version"
git checkout -- .
git status
# get the short commit hash to include in the npm package
current_commit="$(git rev-parse --short HEAD)";
npm version prerelease -preid "${current_commit}" -no-git-tag-version
git commit -a -m "Updating version [skip ci]"
echo "Publish to NPM"
cp .npmrc.template $HOME/.npmrc
npm publish --tag edge
\ No newline at end of file
#!/bin/bash
. "${TRAVIS_BUILD_DIR}/scripts/publish-utils.sh";
if [[ "$TRAVIS_TAG" == "" ]]; then
echo "No tag specified, skipping...";
else
echo "Running on branch/tag ${TRAVIS_TAG}":
echo "Setting up git"
setup_git
echo "Creating new version"
git checkout -- .
git status
npm version -no-git-tag-version $TRAVIS_TAG
echo "Pushing to git"
git commit -a -m "Publishing version ${TRAVIS_TAG} [skip ci]"
git push origin HEAD:${1}
echo "Build and Publish to NPM"
cp .npmrc.template $HOME/.npmrc
if [[ "$TRAVIS_TAG" == *"-beta"* ]]; then
echo "Publishing with beta tag to npm"
npm publish --tag beta
else
echo "Publishing with latest tag to npm"
npm publish
fi
fi
#!/bin/bash
setup_git() {
# Set the user name and email to perform a commit locally. No changes are pushed.
git config --global user.email "user@email.com"
git config --global user.name "username"
}
ensure_version_match() {
VERSION="v$(cat package.json | grep version | cut -f2 -d ":" | tr -d '",\ ')"
TAG="$(echo $GITHUB_REF | grep tags | cut -f3 -d'/')"
[[ "$VERSION" == "$TAG" ]] && echo "Versions match." || exit 1
}
\ No newline at end of file
import { BNInput, ec as EC } from 'elliptic';
import {
Key,
KeyType,
privateKeyToLegacyString,
privateKeyToString,
stringToPrivateKey,
} from './zswjs-numeric';
import { constructElliptic, PublicKey, Signature } from './zswjs-key-conversions';
/** Represents/stores a private key and provides easy conversion for use with `elliptic` lib */
export class PrivateKey {
constructor(private key: Key, private ec: EC) {}
/** Instantiate private key from an `elliptic`-format private key */
public static fromElliptic(privKey: EC.KeyPair, keyType: KeyType, ec?: EC): PrivateKey {
if (!ec) {
ec = constructElliptic(keyType);
}
return new PrivateKey({
type: keyType,
data: privKey.getPrivate().toArrayLike(Buffer, 'be', 32),
}, ec);
}
/** Instantiate private key from an 中数文联盟链-format private key */
public static fromString(keyString: string, ec?: EC): PrivateKey {
const privateKey = stringToPrivateKey(keyString);
if (!ec) {
ec = constructElliptic(privateKey.type);
}
return new PrivateKey(privateKey, ec);
}
/** Export private key as `elliptic`-format private key */
public toElliptic(): EC.KeyPair {
return this.ec.keyFromPrivate(this.key.data);
}
public toLegacyString(): string {
return privateKeyToLegacyString(this.key);
}
/** Export private key as 中数文联盟链-format private key */
public toString(): string {
return privateKeyToString(this.key);
}
/** Get key type from key */
public getType(): KeyType {
return this.key.type;
}
/** Retrieve the public key from a private key */
public getPublicKey(): PublicKey {
const ellipticPrivateKey = this.toElliptic();
return PublicKey.fromElliptic(ellipticPrivateKey, this.getType(), this.ec);
}
/** Sign a message or hashed message digest with private key */
public sign(data: BNInput, shouldHash: boolean = true, encoding: BufferEncoding = 'utf8'): Signature {
if (shouldHash) {
if (typeof data === 'string') {
data = Buffer.from(data, encoding);
}
data = this.ec.hash().update(data).digest();
}
let tries = 0;
let signature: Signature;
const isCanonical = (sigData: Uint8Array): boolean =>
!(sigData[1] & 0x80) && !(sigData[1] === 0 && !(sigData[2] & 0x80))
&& !(sigData[33] & 0x80) && !(sigData[33] === 0 && !(sigData[34] & 0x80));
const constructSignature = (options: EC.SignOptions): Signature => {
const ellipticPrivateKey = this.toElliptic();
const ellipticSignature = ellipticPrivateKey.sign(data, options);
return Signature.fromElliptic(ellipticSignature, this.getType(), this.ec);
};
if (this.key.type === KeyType.k1) {
do {
signature = constructSignature({canonical: true, pers: [++tries]});
} while (!isCanonical(signature.toBinary()));
} else {
signature = constructSignature({canonical: true});
}
return signature;
}
/** Validate a private key */
public isValid(): boolean {
try {
const ellipticPrivateKey = this.toElliptic();
const validationObj = ellipticPrivateKey.validate();
return validationObj.result;
} catch {
return false;
}
}
}
import { ec as EC } from 'elliptic';
import {
Key,
KeyType,
publicKeyToLegacyString,
publicKeyToString,
stringToPublicKey,
} from './zswjs-numeric';
import { constructElliptic } from './zswjs-key-conversions';
/** Represents/stores a public key and provides easy conversion for use with `elliptic` lib */
export class PublicKey {
constructor(private key: Key, private ec: EC) {}
/** Instantiate public key from an 中数文联盟链-format public key */
public static fromString(publicKeyStr: string, ec?: EC): PublicKey {
const key = stringToPublicKey(publicKeyStr);
if (!ec) {
ec = constructElliptic(key.type);
}
return new PublicKey(key, ec);
}
/** Instantiate public key from an `elliptic`-format public key */
public static fromElliptic(publicKey: EC.KeyPair, keyType: KeyType, ec?: EC): PublicKey {
const x = publicKey.getPublic().getX().toArray('be', 32);
const y = publicKey.getPublic().getY().toArray('be', 32);
if (!ec) {
ec = constructElliptic(keyType);
}
return new PublicKey({
type: keyType,
data: new Uint8Array([(y[31] & 1) ? 3 : 2].concat(x)),
}, ec);
}
/** Export public key as 中数文联盟链-format public key */
public toString(): string {
return publicKeyToString(this.key);
}
/** Export public key as Legacy 中数文联盟链-format public key */
public toLegacyString(): string {
return publicKeyToLegacyString(this.key);
}
/** Export public key as `elliptic`-format public key */
public toElliptic(): EC.KeyPair {
return this.ec.keyPair({
pub: Buffer.from(this.key.data),
});
}
/** Get key type from key */
public getType(): KeyType {
return this.key.type;
}
/** Validate a public key */
public isValid(): boolean {
try {
const ellipticPublicKey = this.toElliptic();
const validationObj = ellipticPublicKey.validate();
return validationObj.result;
} catch {
return false;
}
}
}
import { BNInput, ec as EC } from 'elliptic';
import BN = require('bn.js');
import {
Key,
KeyType,
signatureToString,
stringToSignature,
} from './zswjs-numeric';
import { constructElliptic, PublicKey } from './zswjs-key-conversions';
/** Represents/stores a Signature and provides easy conversion for use with `elliptic` lib */
export class Signature {
constructor(private signature: Key, private ec: EC) {}
/** Instantiate Signature from an 中数文联盟链-format Signature */
public static fromString(sig: string, ec?: EC): Signature {
const signature = stringToSignature(sig);
if (!ec) {
ec = constructElliptic(signature.type);
}
return new Signature(signature, ec);
}
/** Instantiate Signature from an `elliptic`-format Signature */
public static fromElliptic(ellipticSig: EC.Signature, keyType: KeyType, ec?: EC): Signature {
const r = ellipticSig.r.toArray('be', 32);
const s = ellipticSig.s.toArray('be', 32);
let zswchainRecoveryParam;
if (keyType === KeyType.k1 || keyType === KeyType.r1) {
zswchainRecoveryParam = ellipticSig.recoveryParam + 27;
if (ellipticSig.recoveryParam <= 3) {
zswchainRecoveryParam += 4;
}
} else if (keyType === KeyType.wa) {
zswchainRecoveryParam = ellipticSig.recoveryParam;
}
const sigData = new Uint8Array([zswchainRecoveryParam].concat(r, s));
if (!ec) {
ec = constructElliptic(keyType);
}
return new Signature({
type: keyType,
data: sigData,
}, ec);
}
/** Export Signature as `elliptic`-format Signature
* NOTE: This isn't an actual elliptic-format Signature, as ec.Signature is not exported by the library.
* That's also why the return type is `any`. We're *actually* returning an object with the 3 params
* not an ec.Signature.
* Further NOTE: @types/elliptic shows ec.Signature as exported; it is *not*. Hence the `any`.
*/
public toElliptic(): any {
const lengthOfR = 32;
const lengthOfS = 32;
const r = new BN(this.signature.data.slice(1, lengthOfR + 1));
const s = new BN(this.signature.data.slice(lengthOfR + 1, lengthOfR + lengthOfS + 1));
let ellipticRecoveryBitField;
if (this.signature.type === KeyType.k1 || this.signature.type === KeyType.r1) {
ellipticRecoveryBitField = this.signature.data[0] - 27;
if (ellipticRecoveryBitField > 3) {
ellipticRecoveryBitField -= 4;
}
} else if (this.signature.type === KeyType.wa) {
ellipticRecoveryBitField = this.signature.data[0];
}
const recoveryParam = ellipticRecoveryBitField & 3;
return { r, s, recoveryParam };
}
/** Export Signature as 中数文联盟链-format Signature */
public toString(): string {
return signatureToString(this.signature);
}
/** Export Signature in binary format */
public toBinary(): Uint8Array {
return this.signature.data;
}
/** Get key type from signature */
public getType(): KeyType {
return this.signature.type;
}
/** Verify a signature with a message or hashed message digest and public key */
public verify(data: BNInput, publicKey: PublicKey, shouldHash: boolean = true, encoding: BufferEncoding = 'utf8'): boolean {
if (shouldHash) {
if (typeof data === 'string') {
data = Buffer.from(data, encoding);
}
data = this.ec.hash().update(data).digest();
}
const ellipticSignature = this.toElliptic();
const ellipticPublicKey = publicKey.toElliptic();
return this.ec.verify(data, ellipticSignature, ellipticPublicKey, encoding);
}
/** Recover a public key from a message or hashed message digest and signature */
public recover(data: BNInput, shouldHash: boolean = true, encoding: BufferEncoding = 'utf8'): PublicKey {
if (shouldHash) {
if (typeof data === 'string') {
data = Buffer.from(data, encoding);
}
data = this.ec.hash().update(data).digest();
}
const ellipticSignature = this.toElliptic();
const recoveredPublicKey = this.ec.recoverPubKey(
data,
ellipticSignature,
ellipticSignature.recoveryParam,
encoding
);
const ellipticKPub = this.ec.keyFromPublic(recoveredPublicKey);
return PublicKey.fromElliptic(ellipticKPub, this.getType(), this.ec);
}
}
import { Api } from './zswjs-api';
import * as ApiInterfaces from './zswjs-api-interfaces';
import { JsonRpc } from './zswjs-jsonrpc';
import * as Numeric from './zswjs-numeric';
import * as RpcInterfaces from './zswjs-rpc-interfaces';
import { RpcError } from './zswjs-rpcerror';
import * as Serialize from './zswjs-serialize';
export { Api, ApiInterfaces, JsonRpc, Numeric, RpcInterfaces, RpcError, Serialize };
This diff is collapsed. Click to expand it.
import { JsonRpc } from './zswjs-jsonrpc';
import { RpcError } from './zswjs-rpcerror';
export { JsonRpc, RpcError };
<svg xmlns="http://www.w3.org/2000/svg" width="186" height="33"><path fill="#000" fill-rule="nonzero" d="M22.4492 32.0313v-5.836h14.3906V8.4766c-.1875-.2657-.6796-.8438-1.4765-1.7344h-12.914V.9062h-7.8985v5.836H.1602v17.3203c0 .0625.039.1094.1171.1406 1.0625 1.2344 1.6407 1.8985 1.7344 1.9922h12.539v5.836h7.8985Zm-7.8984-10.172H8.1055V8.547h.0234l1.3125 2.4843h5.1094v10.8282Zm14.3672 0h-6.4688v-10.828h6.4688v10.828Zm29.6718-5.5546c.0157 0 .0235-.0078.0235-.0235v-6h8.2969V5.9688h-8.297V.9063h-6.4687v5.0625h-8.625l1.5938 4.3125h7.0312v6c0 .0157.0157.0235.047.0235h6.3983ZM74.082 32.0312c.9375-1.3437 1.414-2.0156 1.4297-2.0156v.0235c.9219 1.2968 1.4063 1.9609 1.4531 1.9921h7.8516l-5.3672-7.5c1.75-2.4687 2.6563-3.75 2.7188-3.8437.5937-.7969.8906-1.2188.8906-1.2656V8.7344h.8203V3.7187h-8.8594c.25-1.3125.4375-2.25.5625-2.8125h-6.4922c-.0937.5157-.8046 4.0704-2.1328 10.6641h6.4688c.2031-1.0625.3125-1.5937.3281-1.5937.1406-.8282.2266-1.2422.2578-1.2422h2.5547v9.2812c0 .0157-.289.4375-.8672 1.2657h-.0234c0-.0157-.086-.1407-.2578-.375-.422-.5626-.6328-.8672-.6328-.9141v-5.625h-6.8204v7.0547c0 .0468.1329.25.3985.6093 2.2656 3.1876 3.4219 4.797 3.4687 4.8282v.0234c-.0156.0313-1.664 2.414-4.9453 7.1485h7.1953ZM51.7695 5.1954V5.172c0-.0313-.4687-1.2266-1.4062-3.586h-6.8438c.2287.5817.4226 1.0724.5817 1.4722l.1278.3204c.2586.6459.397.978.4155.9965.1875.5469.297.8203.3282.8203h6.7968Zm13.9922 0a1.9143 1.9143 0 0 0 .0238-.0573l.0364-.0899c.0964-.2394.3081-.77.635-1.5915l.1591-.3998c.1673-.4205.359-.903.5754-1.4474v-.0235h-6.7969l-1.4297 3.5625c0 .0313.0079.047.0235.047h6.7734ZM50.3633 16.3047l1.4297-5.3672h-6.8672l-1.4063 5.3672h6.8438Zm16.8281 0c-.0312-.1563-.5-1.9453-1.4062-5.3672h-6.8204c0 .0156.375 1.4531 1.125 4.3125.1875.7031.297 1.0547.3282 1.0547h6.7734ZM53.1992 32.0312a.0428.0428 0 0 0 .012-.003l.055-.0247c.2277-.111 1.0178-.5548 2.3705-1.3316.0469.0468.8281.5 2.3438 1.3593h8.25v-.6796c0-.0157-.1328-.1094-.3985-.2813-2.953-1.8281-4.6797-2.9219-5.1797-3.2812v-.0235h.0235c2.8437-1.625 4.3125-2.461 4.4062-2.5078v-2.6015h1.7813c.0312 0 .0469-.0079.0469-.0235v-4.2656H65.082v-1.3594h-6.4687v1.3594h-5.9531c-.0313 0-.047-.0156-.047-.0469v-1.3125H46.168v1.3594h-2.6485l1.5938 4.289h1.0547v2.6016l4.4297 2.5313c-.1407.1093-1.1641.7656-3.0704 1.9687-1.5156.9375-2.3046 1.4453-2.3671 1.5235v.75h8.039Zm2.4375-7.3828c-1.9531-1.2187-2.961-1.8671-3.0234-1.9453v-.0468h6v.0937c-1.9375 1.2031-2.9297 1.836-2.9766 1.8984Zm41.3203 7.3829.0662-.033c.4346-.2233 2.3031-1.267 5.6057-3.1311l.8116-.4492.4952-.2743c2.0663-1.1452 3.0995-1.7243 3.0995-1.7374.0312 0 .078.0156.1406.0468l7.4297 4.336c1.2968.7812 2.0078 1.1953 2.1328 1.2421h10.3828v-1.0078c-.25-.125-.7266-.3906-1.4297-.7968-7.91-4.574-12.072-6.9894-12.4859-7.2466l-.0297-.019c.3125-.172 1.8515-1.0313 4.6172-2.5782l.29-.1631a969.8684 969.8684 0 0 1 3.1005-1.7377l.2965-.165c.5102-.2835.8358-.462.977-.5358v-9h4.664V4.4688h-15.4452c-.2656-1-.5625-2.1875-.8906-3.5625h-7.547c.2032.8282.4923 2.0157.8673 3.5625H86.8789l1.5938 4.3125h3.8437v9c.1406.0782 1.9688 1.1485 5.4844 3.211 2.2031 1.2812 3.3125 1.9219 3.3281 1.9219 0 .0312-.0312.0625-.0937.0937-8.6563 4.8125-13.375 7.4297-14.1563 7.8516v1.1718H96.957Zm10.289-12.4922c-3.2812-1.875-4.9687-2.8594-5.0624-2.9532-1.2125-.675-1.95-1.095-2.2125-1.26l-.0762-.0499c-.018-.0126-.0285-.0213-.0316-.026V8.7812h15.0469v6.4922c-.5782.3125-3.1329 1.7344-7.6641 4.2657Zm45.9376 9.4453V3.3437h-6.5156v21.9376h-9.9375V18.578h-6.4922v8.8828c.6406.5313.9922.8204 1.0547.8672.5.4375.789.6563.8672.6563h21.0234Zm28.1484 0c.284-.013 1.2664-.0205 2.9472-.0228l.554-.0005.6004-.0002h.0234V15.4375c-.1875-.1875-.7656-.6563-1.7343-1.4063h-19.6172v-7.078h14.4843v3.7734h6.8204c.0312 0 .0468-.0157.0468-.047V4.797c-.2812-.2344-.961-.7578-2.039-1.5703h-26.1797v12.9843c.0312.0313.711.5391 2.039 1.5235h19.3125v7.5468h-14.4843v-3.7734h-6.8672v5.9531l2.0156 1.5c.1784 0 .3493 0 .5129.0002l.6856.0009c1.3416.0025 2.0124.01 2.0124.0224 1.75-.0157 3.2891-.0235 4.6172-.0235h6.4688c2.2969 0 4.8906.0079 7.7812.0235Z"/></svg>
\ No newline at end of file
const fs = require('fs');
const path = require('path');
const { JsonRpc, RpcError, Api } = require('../../dist');
const { JsSignatureProvider } = require('../../dist/zswjs-jssig');
const fetch = require('node-fetch');
const { TextEncoder, TextDecoder } = require('util');
const privateKey = '5JuH9fCXmU3xbj8nRmhPZaVrxxXrdPaRmZLW1cznNTmTQR2Kg5Z'; // replace with "bob" account private key
const r1PrivateKey = 'PVT_R1_GrfEfbv5at9kbeHcGagQmvbFLdm6jqEpgE1wsGbrfbZNjpVgT';
const cfactorPrivateKey = '5K8Sm2bB2b7ZC8tJMefrk1GFa4jgtHxxHRcjX49maMk9AEwq8hN';
/* new accounts for testing can be created by unlocking a clzsw wallet then calling:
* 1) clzsw create key --to-console (copy this privateKey & publicKey)
* 2) clzsw wallet import
* 3) clzsw create account bob publicKey
* 4) clzsw create account alice publicKey
*/
const rpc = new JsonRpc('http://localhost:8888', { fetch });
const signatureProvider = new JsSignatureProvider([privateKey, r1PrivateKey, cfactorPrivateKey]);
const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), textEncoder: new TextEncoder() });
const transactWithConfig = async (config, memo, from = 'bob', to = 'alice') => {
return await api.transact({
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: from,
permission: 'active',
}],
data: {
from,
to,
quantity: '0.0001 SYS',
memo,
},
}]
}, config);
};
const transactWithoutConfig = async () => {
const transactionResponse = await transactWithConfig({ blocksBehind: 3, expireSeconds: 30}, 'transactWithoutConfig');
const blockInfo = await rpc.get_block_info(transactionResponse.processed.block_num - 3);
const currentDate = new Date();
const timePlusTen = currentDate.getTime() + 10000;
const timeInISOString = (new Date(timePlusTen)).toISOString();
const expiration = timeInISOString.substr(0, timeInISOString.length - 1);
return await api.transact({
expiration,
ref_block_num: blockInfo.block_num & 0xffff,
ref_block_prefix: blockInfo.ref_block_prefix,
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'bob',
permission: 'active',
}],
data: {
from: 'bob',
to: 'alice',
quantity: '0.0001 SYS',
memo: 'transactWithoutConfig2',
},
}]
});
};
const transactWithContextFreeAction = async () => {
return await api.transact({
actions: [{
account: 'cfhello',
name: 'normal',
authorization: [{
actor: 'cfactor',
permission: 'active'
}],
data: {
user: 'test'
}
}],
context_free_actions: [{
account: 'cfhello',
name: 'contextfree',
authorization: [],
data: {}
}]
}, {
blocksBehind: 3,
expireSeconds: 30
});
};
const transactWithContextFreeData = async () => {
return await api.transact({
actions:[{
account: 'cfhello',
name: 'normal',
authorization: [{
actor: 'cfactor',
permission: 'active'
}],
data: {
user: 'test2'
}
}],
context_free_actions: [{
account: 'cfhello',
name: 'contextfree',
authorization: [],
data: {}
}],
context_free_data: [[ '74657374', '7465737464617461' ]]
}, {
blocksBehind: 3,
expireSeconds: 30
});
};
const transactWithShorthandApiJson = async () => {
await api.getAbi('zsw.token');
return await api.transact({
actions: [
api.with('zsw.token').as('bob').transfer('bob', 'alice', '0.0001 SYS', 'transactWithShorthandApiJson')
]
}, {
blocksBehind: 3,
expireSeconds: 30
});
};
const transactWithShorthandTxJson = async () => {
await api.getAbi('zsw.token');
const tx = api.buildTransaction();
tx.with('zsw.token').as('bob').transfer('bob', 'alice', '0.0001 SYS', 'transactWithShorthandTxJson');
return await tx.send({
blocksBehind: 3,
expireSeconds: 30
});
};
const transactWithShorthandTxJsonContextFreeAction = async () => {
await api.getAbi('cfhello');
const tx = api.buildTransaction();
tx.associateContextFree(() => ({
contextFreeAction: tx.with('cfhello').as().contextfree(),
action: tx.with('cfhello').as('cfactor').normal('test')
}));
return await tx.send({
blocksBehind: 3,
expireSeconds: 30
});
};
const transactWithShorthandTxJsonContextFreeData = async () => {
await api.getAbi('cfhello');
const tx = api.buildTransaction();
tx.associateContextFree(() => ({
contextFreeData: [ '74657374', '7465737464617461' ],
contextFreeAction: tx.with('cfhello').as().contextfree(),
action: tx.with('cfhello').as('cfactor').normal('test2')
}));
return await tx.send({
blocksBehind: 3,
expireSeconds: 30
});
};
const transactWithReturnValue = async () => {
await api.getAbi('returnvalue');
const tx = api.buildTransaction();
tx.with('returnvalue').as('bob').sum(5, 5);
return await tx.send({
blocksBehind: 3,
expireSeconds: 30
});
};
const transactWithResourcePayer = async () => {
return await api.transact({
resource_payer: {
payer: 'alice',
max_net_bytes: 4096,
max_cpu_us: 400,
max_memory_bytes: 0
},
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'bob',
permission: 'active',
}, {
actor: 'alice',
permission: 'active',
}],
data: {
from: 'bob',
to: 'alice',
quantity: '0.0001 SYS',
memo: 'resource payer',
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30
});
};
const readOnlyQuery = async () => {
return await api.transact({
actions: [{
account: 'readonly',
name: 'get',
authorization: [{
actor: 'readonly',
permission: 'active',
}],
data: {},
}],
}, {
blocksBehind: 3,
expireSeconds: 30,
compression: true,
readOnlyTrx: true,
});
};
const readOnlyFailureTrace = async () => {
return await api.transact({
actions: [{
account: 'zswchain',
name: 'setpriv',
authorization: [{
actor: 'bob',
permission: 'active',
}],
data: {
account: 'bob',
is_priv: '1'
},
}]
}, {
blocksBehind: 3,
expireSeconds: 30,
readOnlyTrx: true,
returnFailureTraces: true,
});
};
const broadcastResult = async (signaturesAndPackedTransaction) => await api.pushSignedTransaction(signaturesAndPackedTransaction);
const transactShouldFail = async () => await api.transact({
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'bob',
permission: 'active',
}],
data: {
from: 'bob',
to: 'alice',
quantity: '0.0001 SYS',
memo: '',
},
}]
});
const rpcShouldFail = async () => await rpc.get_block_info(-1);
module.exports = {
transactWithConfig,
transactWithoutConfig,
transactWithContextFreeAction,
transactWithContextFreeData,
broadcastResult,
transactShouldFail,
transactWithShorthandApiJson,
transactWithShorthandTxJson,
transactWithShorthandTxJsonContextFreeAction,
transactWithShorthandTxJsonContextFreeData,
transactWithReturnValue,
transactWithResourcePayer,
readOnlyQuery,
readOnlyFailureTrace,
rpcShouldFail
};
const tests = require('./node');
describe('Node JS environment', () => {
let transactionResponse: any;
let transactionSignatures: any;
let failedAsPlanned: boolean;
it('node tests not required for this suite, see official node tests',async()=>{
expect(1).toBe(1);
})
/*
it('transacts with configuration object containing blocksBehind', async () => {
transactionResponse = await tests.transactWithConfig({
blocksBehind: 3,
expireSeconds: 30
}, 'transactWithBlocksBehind');
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
it('transacts with configuration object containing useLastIrreversible', async () => {
transactionResponse = await tests.transactWithConfig({
useLastIrreversible: true,
expireSeconds: 30
}, 'transactWithUseLastIrreversible');
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
it('transacts with manually configured TAPOS fields', async () => {
if (process.env.NODZSW_VER && process.env.NODZSW_VER === 'release/2.0.x') return;
transactionResponse = await tests.transactWithoutConfig();
expect(Object.keys(transactionResponse)).toContain('transaction_id');
}, 10000);
it('transacts with compressed transaction', async () => {
transactionResponse = await tests.transactWithConfig({
blocksBehind: 3,
expireSeconds: 30,
compression: true
}, 'transactWithCompression');
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
it('transacts with context free action', async () => {
transactionResponse = await tests.transactWithContextFreeAction();
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
it('transacts with context free data', async () => {
transactionResponse = await tests.transactWithContextFreeData();
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
it('transacts without broadcasting, returning signatures and packed transaction', async () => {
transactionSignatures = await tests.transactWithConfig({
broadcast: false,
blocksBehind: 3,
expireSeconds: 30
}, 'transactWithoutBroadcast');
expect(Object.keys(transactionSignatures)).toContain('signatures');
expect(Object.keys(transactionSignatures)).toContain('serializedTransaction');
});
it('broadcasts packed transaction, given valid signatures', async () => {
transactionSignatures = await tests.transactWithConfig({
broadcast: false,
blocksBehind: 3,
expireSeconds: 30
}, 'transactWithoutBroadcast2');
transactionResponse = await tests.broadcastResult(transactionSignatures);
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
describe('Json Abi with Shorthand Design', () => {
it('transacts with shorthand structure using api', async () => {
transactionResponse = await tests.transactWithShorthandApiJson();
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
it('transacts with shorthand structure using tx', async () => {
transactionResponse = await tests.transactWithShorthandTxJson();
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
it('transacts with shorthand structure using tx and context free action', async () => {
transactionResponse = await tests.transactWithShorthandTxJsonContextFreeAction();
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
it('transacts with shorthand structure using tx and context free data', async () => {
transactionResponse = await tests.transactWithShorthandTxJsonContextFreeData();
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
});
it('transacts with elliptic p256/KeyType.R1 keys and signatures', async () => {
transactionResponse = await tests.transactWithConfig({
blocksBehind: 3,
expireSeconds: 30
}, 'transactWithR1KeySignature', 'bobr1', 'alicer1');
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
it('confirms an action\'s return value can be verified', async () => {
if (process.env.NODZSW_VER && process.env.NODZSW_VER === 'release/2.0.x') return;
const expectedValue = 10;
transactionResponse = await tests.transactWithReturnValue();
expect(transactionResponse.processed.action_traces[0].return_value_data).toEqual(expectedValue);
});
it('transacts with resource payer', async () => {
if (process.env.NODZSW_VER && (process.env.NODZSW_VER === 'release/2.0.x' || process.env.NODZSW_VER === 'release/2.1.x')) return;
transactionResponse = await tests.transactWithResourcePayer();
expect(Object.keys(transactionResponse)).toContain('transaction_id');
});
it('confirms the return value of the read-only query', async () => {
if (process.env.NODZSW_VER && (process.env.NODZSW_VER === 'release/2.0.x' || process.env.NODZSW_VER === 'release/2.1.x')) return;
const expectedValue = [
{'age': 25, 'gender': 1, 'id': 1, 'name': 'Bob Smith'},
{'age': 42, 'gender': 1, 'id': 3, 'name': 'John Smith'},
{'age': 27, 'gender': 1, 'id': 4, 'name': 'Jack Smith'},
{'age': 20, 'gender': 0, 'id': 2, 'name': 'Alice Smith'},
{'age': 26, 'gender': 0, 'id': 5, 'name': 'Youko Niihara'},
{'age': 18, 'gender': 0, 'id': 6, 'name': 'Rose Lee'},
{'age': 25, 'gender': 0, 'id': 7, 'name': 'Youko Kawakami'},
{'age': 24, 'gender': 0, 'id': 8, 'name': 'Yuu Yamada'}
];
transactionResponse = await tests.readOnlyQuery();
expect(transactionResponse.result.action_traces[0].return_value_data).toEqual(expectedValue);
});
it('returns failure trace for failed transaction', async () => {
if (process.env.NODZSW_VER && (process.env.NODZSW_VER === 'release/2.0.x' || process.env.NODZSW_VER === 'release/2.1.x')) return;
try {
await tests.readOnlyFailureTrace();
} catch (e) {
expect(e.details.code).toEqual(3090004);
expect(e.details.stack[0].format).toEqual('missing authority of ${account}');
}
});
it('throws appropriate error message without configuration object or TAPOS in place', async () => {
try {
failedAsPlanned = true;
await tests.transactShouldFail();
failedAsPlanned = false;
} catch (e) {
if (e.message !== 'Required configuration or TAPOS fields are not present') {
failedAsPlanned = false;
}
}
expect(failedAsPlanned).toEqual(true);
});
it('throws an an error with RpcError structure for invalid RPC calls', async () => {
try {
failedAsPlanned = true;
await tests.rpcShouldFail();
failedAsPlanned = false;
} catch (e) {
if (!e.json || !e.json.error || !(e.json.error.hasOwnProperty('details'))) {
failedAsPlanned = false;
}
}
expect(failedAsPlanned).toEqual(true);
});
*/
});
global.fetch = require('jest-fetch-mock');
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
font-family: 'Source Sans Pro', sans-serif;
}
button {
font-family: 'Source Sans Pro', sans-serif;
font-size: .9rem;
font-weight: 600;
cursor: pointer;
}
button:active, button:focus {
outline: none;
}
.header-container {
margin: 0;
background-color: #F6F6F8;
}
.header {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
max-width: 1200px;
margin : 0 auto;
padding: 2rem;
}
.header > img {
max-width: 8rem;
}
.header > h1 {
font-size: 3.5rem;
font-weight: lighter;
color: #202035;
margin: 2rem 0;
}
.header > button {
padding: 1rem 6rem;
border-radius: .4rem;
color: white;
background: #15087E;
letter-spacing: 1px;
}
.header > button:hover {
background: #15089E;
}
.tests {
max-width: 1200px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: 0 auto;
padding: 0 1rem;
}
.tests > div {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
text-align: center;
width: 30%;
margin: 1rem auto;
}
.tests > div > h2 {
font-weight: normal;
margin: 1.5rem 0;
font-size: .9rem;
}
.tests > div > button {
width: 6.5rem;
height: 6.5rem;
margin: 1rem;;
padding: 0;
border: .15rem solid #15087E;
border-radius: 5rem;
}
.tests > div > button:hover {
background: #F6F6F8;
}
.tests > div > button.success {
color: #7ED321;
border: .15rem solid #7ED321;
}
.tests > div > button.failed {
color: #FF6363;
border: .15rem solid #FF6363;
}
@media (min-width: 1200px) {
html, body {
font-size: 20px;
}
}
@media (max-width: 800px) {
html, body {
font-size: 14px;
}
}
const ecc = require('zsw-crypto');
import { ecc as eccMigration } from '../zsw-crypto-migration';
import { PrivateKey } from '../zswjs-key-conversions';
describe('ecc Migration', () => {
const privateKeys = [
'5Juww5SS6aLWxopXBAWzwqrwadiZKz7XpKAiktXTKcfBGi1DWg8',
'5JnHjSFwe4r7xyqAUAaVs51G7HmzE86DWGa3VAA5VvQriGYnSUr',
'5K4XZH5XR2By7Q5KTcZnPAmUMU5yjUNBdoKzzXyrLfmiEZJqoKE',
];
const legacyPublicKeys = [
'ZSW7tgwU6E7pAUQJgqEJt66Yi8cWvanTUW8ZfBjeXeJBQvhTU9ypi',
'ZSW8VaY5CiTexYqgQZyPTJkc3qvWuZUi12QrZL9ssjqW2es6aQk2F',
'ZSW7VGhqctkKprW1VUj19DZZiiZLX3YcJqUJCuEcahJmUCw3wJEMu',
];
it('verifies `initialize` returns console.error message', () => {
console.error = jest.fn();
eccMigration.initialize();
expect(console.error).toHaveBeenCalledWith('Method deprecated');
});
it('verifies `unsafeRandomKey` returns console.error message', () => {
console.error = jest.fn();
eccMigration.unsafeRandomKey();
expect(console.error).toHaveBeenCalledWith('Method deprecated');
});
it('verifies `randomKey` calls generateKeyPair', async () => {
console.warn = jest.fn();
const privateKey = await eccMigration.randomKey(0, { secureEnv: true });
expect(console.warn).toHaveBeenCalledWith('Argument `cpuEntropyBits` is deprecated, ' +
'use the options argument instead');
expect(typeof privateKey).toEqual('string');
expect(PrivateKey.fromString(privateKey).isValid()).toBeTruthy();
});
it('verifies `seedPrivate` returns console.error message', () => {
console.error = jest.fn();
eccMigration.seedPrivate();
expect(console.error).toHaveBeenCalledWith('Method deprecated');
});
it('verifies `privateToPublic` function is consistent between ecc objects', () => {
console.warn = jest.fn();
const eccPublicKey = ecc.privateToPublic(privateKeys[0], 'ZSW');
const eccMigrationPublicKey = eccMigration.privateToPublic(privateKeys[0], 'ZSW');
expect(console.warn).toHaveBeenCalledWith('Argument `pubkey_prefix` is deprecated, ' +
'keys prefixed with PUB_K1_/PUB_R1_/PUB_WA_ going forward');
expect(eccPublicKey).toEqual(eccMigrationPublicKey);
});
it('verifies `isValidPublic` function is consistent between ecc objects', () => {
console.warn = jest.fn();
const eccValid = ecc.isValidPublic(legacyPublicKeys[0], 'ZSW');
const eccMigrationValid = eccMigration.isValidPublic(legacyPublicKeys[0], 'ZSW');
expect(console.warn).toHaveBeenCalledWith('Argument `pubkey_prefix` is deprecated, ' +
'keys prefixed with PUB_K1_/PUB_R1_/PUB_WA_ going forward');
expect(eccValid).toEqual(eccMigrationValid);
expect(eccValid).toBeTruthy();
expect(eccMigrationValid).toBeTruthy();
});
it('verifies `isValidPublic` function is consistent during an error', () => {
console.warn = jest.fn();
const eccValid = ecc.isValidPublic('publickey', 'ZSW');
const eccMigrationValid = eccMigration.isValidPublic('publickey', 'ZSW');
expect(console.warn).toHaveBeenCalledWith('Argument `pubkey_prefix` is deprecated, ' +
'keys prefixed with PUB_K1_/PUB_R1_/PUB_WA_ going forward');
expect(eccValid).toEqual(eccMigrationValid);
expect(eccValid).toBeFalsy();
expect(eccMigrationValid).toBeFalsy();
});
it('verifies `isValidPrivate` function is consistent between ecc objects', () => {
const eccValid = ecc.isValidPrivate(privateKeys[0]);
const eccMigrationValid = eccMigration.isValidPrivate(privateKeys[0]);
expect(eccValid).toEqual(eccMigrationValid);
expect(eccValid).toBeTruthy();
expect(eccMigrationValid).toBeTruthy();
});
it('verifies `isValidPrivate` function is consistent during an error', () => {
const eccValid = ecc.isValidPrivate('privatekey');
const eccMigrationValid = eccMigration.isValidPrivate('privatekey');
expect(eccValid).toEqual(eccMigrationValid);
expect(eccValid).toBeFalsy();
expect(eccMigrationValid).toBeFalsy();
});
it('verifies `sign`, `recover`, and `verify` functions are consistent between ecc objects', () => {
const dataAsString = 'some string';
const eccSig = ecc.sign(dataAsString, privateKeys[0], 'utf8');
const eccMigrationSig = eccMigration.sign(dataAsString, privateKeys[0], 'utf8');
// signatures are different
expect(eccSig).not.toEqual(eccMigrationSig);
const eccKPub = ecc.recover(eccSig, dataAsString, 'utf8');
const eccMigrationKPub = eccMigration.recover(eccMigrationSig, dataAsString, 'utf8');
expect(eccKPub).toEqual(eccMigrationKPub);
});
it('verifies `signHash`, `recoverHash`, and `sha256` functions are consistent between ecc objects', () => {
console.warn = jest.fn();
const dataAsString = 'some string';
const eccHash = Buffer.from(ecc.sha256(dataAsString), 'hex');
const eccMigrationHash = Buffer.from(eccMigration.sha256(dataAsString, 'hex', 'utf8') as string, 'hex');
expect(console.warn).toBeCalledWith('Argument `encoding` is deprecated');
expect(console.warn).toBeCalledWith('Argument `resultEncoding` is deprecated');
expect(eccHash).toEqual(eccMigrationHash);
const eccSig = ecc.signHash(eccHash, privateKeys[0], 'utf8');
const eccMigrationSig = eccMigration.signHash(eccMigrationHash, privateKeys[0], 'utf8');
// signatures are different
expect(eccSig).not.toEqual(eccMigrationSig);
const eccKPub = ecc.recoverHash(eccSig, eccHash, 'utf8');
const eccMigrationKPub = eccMigration.recoverHash(eccSig, eccMigrationHash, 'utf8');
expect(eccKPub).toEqual(eccMigrationKPub);
});
});
// zsw-crypto stuff
const ecc = require('zsw-crypto');
const { ec } = require('elliptic');
const { Signature, PrivateKey, PublicKey, sha256 } = require('../zswjs-key-conversions');
const {
JsSignatureProvider,
} = require('../zswjs-jssig');
const { KeyType } = require('../zswjs-numeric');
const { SignatureProviderArgs } = require('../zswjs-api-interfaces');
describe('JsSignatureProvider', () => {
const privateKeys = [
'5Juww5SS6aLWxopXBAWzwqrwadiZKz7XpKAiktXTKcfBGi1DWg8',
'5JnHjSFwe4r7xyqAUAaVs51G7HmzE86DWGa3VAA5VvQriGYnSUr',
'5K4XZH5XR2By7Q5KTcZnPAmUMU5yjUNBdoKzzXyrLfmiEZJqoKE',
];
const legacyPublicKeys = [
'ZSW7tgwU6E7pAUQJgqEJt66Yi8cWvanTUW8ZfBjeXeJBQvhTU9ypi',
'ZSW8VaY5CiTexYqgQZyPTJkc3qvWuZUi12QrZL9ssjqW2es6aQk2F',
'ZSW7VGhqctkKprW1VUj19DZZiiZLX3YcJqUJCuEcahJmUCw3wJEMu',
];
const k1FormatPublicKeys = [
'PUB_K1_7tgwU6E7pAUQJgqEJt66Yi8cWvanTUW8ZfBjeXeJBQvhYTBFvY',
'PUB_K1_8VaY5CiTexYqgQZyPTJkc3qvWuZUi12QrZL9ssjqW2es7e7bRJ',
'PUB_K1_7VGhqctkKprW1VUj19DZZiiZLX3YcJqUJCuEcahJmUCw9RT8v2',
];
const signatures = [
'SIG_K1_HKkqi3zray76i63ZQwAHWMjoLk3wTa1ajZWPcUnrhgmSWQYEHDJsxkny6VDTWEmVdfktxpGoTA81qe6QuCrDmazeQndmxh',
'SIG_K1_HCaY9Y9qdjnkRhE9hokAyp3pFtkMmjpxF6xTd514Vo8vLVSWKek5m5aHfCaka9TqZUbajkhhd4BfBLxSwCwZUEmy8cvt1x',
'SIG_K1_GrZqp9ZkuhBeNpeQ5b2L2UWUUrNU1gHbTyMzkyWRhiXNkxPP84Aq9eziU399eBf9xJw8MqHHjz7R2wMTMXhXjHLgpZYFeA',
];
const eccSignatures = [
'SIG_K1_KeEyJFpkp63Qq5E1zRD9aNZtTjpStvdkdnL31Z7wVmhYtrKGtpVdMBJnXyEUXNkNEyo4d4i4Q79qmRpCUsCRdFqhV6KAeF',
'SIG_K1_JvgMmFSDhipS1SeBLNBMdAxayAsWS3GuVGSHS7YQth5Z5ZpijxnZgaa23dYD1efQhpEgtEggdRfHMmp31RDXjmJdZYoKLm',
'SIG_K1_JwMqV2nbEntHSq9AuG3Zq1JBc5YqD2SftMHCTGK4A8DYGn1VPQ8QAduwCNksT5JhYgAmGMzPyJdZ2Ws4p8TCvQ16LeNhrw',
];
// These are simplified tests simply to verify a refactor didn't mess with existing code
it('(NOTE: sigs are different): ensure elliptic does what zsw-crypto used to do', () => {
const ellipticEc = new ec('secp256k1');
for (let idx=0; idx<privateKeys.length; idx++) {
const KPriv = privateKeys[idx];
const KPrivElliptic = PrivateKey.fromString(KPriv).toElliptic();
const KPubK1 = new JsSignatureProvider([KPriv]).availableKeys[0];
const dataAsString = 'some string';
const eccHashedString = Buffer.from(ecc.sha256(dataAsString), 'hex');
const ellipticHashedStringAsBuffer = Buffer.from(ellipticEc.hash().update(dataAsString).digest(), 'hex');
expect(eccHashedString).toEqual(ellipticHashedStringAsBuffer);
const eccSig = ecc.sign(dataAsString, KPriv, 'utf8');
const ellipticSig = KPrivElliptic.sign(ellipticHashedStringAsBuffer, 'utf8');
const eccKPub = ecc.recover(eccSig, dataAsString, 'utf8');
const ellipticRecoveredKPub = ellipticEc.recoverPubKey(
ellipticHashedStringAsBuffer,
ellipticSig,
ellipticSig.recoveryParam,
'utf8'
);
const ellipticKPub = ellipticEc.keyFromPublic(ellipticRecoveredKPub);
expect(PublicKey.fromElliptic(ellipticKPub, KeyType.k1).toString()).toEqual(k1FormatPublicKeys[idx]);
const eccValid = ecc.verify(eccSig, dataAsString, eccKPub, 'utf8');
const ellipticValid = ellipticEc.verify(
ellipticHashedStringAsBuffer,
ellipticSig,
ellipticEc.keyFromPublic(ellipticKPub),
'utf8'
);
expect(eccValid).toEqual(true);
expect(ellipticValid).toEqual(true);
}
});
it('ensure elliptic verifies zsw-crypto\'s Sigs', () => {
const ellipticEc = new ec('secp256k1');
for (let idx=0; idx<privateKeys.length; idx++) {
const KPriv = privateKeys[idx];
const KPrivElliptic = PrivateKey.fromString(KPriv).toElliptic();
const KPubK1 = new JsSignatureProvider([KPriv]).availableKeys[0];
const dataAsString = 'some string';
const eccHashedString = Buffer.from(ecc.sha256(dataAsString), 'hex');
const ellipticHashedStringAsBuffer = Buffer.from(ellipticEc.hash().update(dataAsString).digest(), 'hex');
expect(eccHashedString).toEqual(ellipticHashedStringAsBuffer);
const eccSig = ecc.sign(dataAsString, KPriv, 'utf8');
const ellipticSig = Signature.fromString(eccSig).toElliptic();
const recoveredKPub = ecc.recover(eccSig, dataAsString, 'utf8');
const ellipticRecoveredKPub = ellipticEc.recoverPubKey(
ellipticHashedStringAsBuffer,
ellipticSig,
ellipticSig.recoveryParam,
'utf8'
);
const ellipticKPub = ellipticEc.keyFromPublic(ellipticRecoveredKPub);
expect(PublicKey.fromElliptic(ellipticKPub, KeyType.k1).toString()).toEqual(PublicKey.fromString(recoveredKPub).toString());
expect(PublicKey.fromElliptic(ellipticKPub, KeyType.k1).toString()).toEqual(k1FormatPublicKeys[idx]);
const ellipticValid = ellipticEc.verify(
ellipticHashedStringAsBuffer,
ellipticSig,
ellipticEc.keyFromPublic(ellipticKPub),
'utf8'
);
expect(ellipticValid).toEqual(true);
}
});
it('ensure ecc verifies elliptic\'s Sigs', () => {
const ellipticEc = new ec('secp256k1');
for (let idx=0; idx<privateKeys.length; idx++) {
const KPriv = privateKeys[idx];
const KPrivElliptic = PrivateKey.fromString(KPriv).toElliptic();
const KPubK1 = new JsSignatureProvider([KPriv]).availableKeys[0];
const dataAsString = 'some string';
const ellipticHashedStringAsBuffer = Buffer.from(ellipticEc.hash().update(dataAsString).digest(), 'hex');
const ellipticSig = KPrivElliptic.sign(ellipticHashedStringAsBuffer, 'utf8');
const ellipticSigAsString = Signature.fromElliptic(ellipticSig, KeyType.k1).toString();
const recoveredKPub = ecc.recover(ellipticSigAsString, dataAsString, 'utf8');
const ellipticRecoveredKPub = ellipticEc.recoverPubKey(
ellipticHashedStringAsBuffer,
ellipticSig,
ellipticSig.recoveryParam,
'utf8'
);
const ellipticKPub = ellipticEc.keyFromPublic(ellipticRecoveredKPub);
expect(PublicKey.fromElliptic(ellipticKPub, KeyType.k1).toString()).toEqual(k1FormatPublicKeys[idx]);
const eccValid = ecc.verify(ellipticSigAsString, dataAsString, recoveredKPub, 'utf8');
expect(eccValid).toEqual(true);
}
});
it('ensure zswjs verifies zsw-crypto\'s Sigs', () => {
for (let idx=0; idx<privateKeys.length; idx++) {
const KPriv = privateKeys[idx];
const privateKey = PrivateKey.fromString(KPriv);
const dataAsString = 'some string';
const eccHashedString = Buffer.from(ecc.sha256(dataAsString), 'hex');
const zswjsHashedStringAsBuffer = Buffer.from(sha256(dataAsString), 'hex');
expect(eccHashedString).toEqual(zswjsHashedStringAsBuffer);
const eccSig = ecc.sign(dataAsString, KPriv, 'utf8');
const zswjsSig = Signature.fromString(eccSig);
const recoveredKPub = ecc.recover(eccSig, dataAsString, 'utf8');
const zswjsRecoveredKPub = zswjsSig.recover(dataAsString, true, 'utf8');
expect(zswjsRecoveredKPub.toLegacyString()).toEqual(recoveredKPub);
expect(zswjsRecoveredKPub.toString()).toEqual(k1FormatPublicKeys[idx]);
const zswjsValid = zswjsSig.verify(dataAsString, zswjsRecoveredKPub, true, 'utf8');
expect(zswjsValid).toEqual(true);
}
});
it('ensure ecc verifies zswjs\'s Sigs', () => {
for (let idx=0; idx<privateKeys.length; idx++) {
const KPriv = privateKeys[idx];
const privateKey = PrivateKey.fromString(KPriv);
const dataAsString = 'some string';
const zswjsHashedStringAsBuffer = Buffer.from(sha256(dataAsString), 'hex');
const zswjsSig = privateKey.sign(zswjsHashedStringAsBuffer, false, 'utf8');
const zswjsSigAsString = zswjsSig.toString();
const recoveredKPub = ecc.recover(zswjsSigAsString, dataAsString, 'utf8');
const zswjsRecoveredKPub = zswjsSig.recover(dataAsString, true, 'utf8');
expect(zswjsRecoveredKPub.toLegacyString()).toEqual(recoveredKPub);
expect(zswjsRecoveredKPub.toString()).toEqual(k1FormatPublicKeys[idx]);
const eccValid = ecc.verify(zswjsSigAsString, dataAsString, recoveredKPub, 'utf8');
expect(eccValid).toEqual(true);
}
});
});
const { TextEncoder, TextDecoder } = require('util');
import { ec } from 'elliptic';
import { createInitialTypes, Type, SerialBuffer } from '../zswjs-serialize';
describe('Serialize', () => {
let types: Map<string, Type>;
beforeAll(() => {
types = createInitialTypes();
});
it('should be able to createInitialTypes', () => {
expect(types).toBeTruthy();
});
describe('pushAsset', () => {
let serialBuffer: SerialBuffer;
const genericValidSymbolCharacter = 'A';
const invalidSymbolErrorMessage = 'Expected symbol to be A-Z and between one and seven characters';
beforeEach(() => {
serialBuffer = new SerialBuffer({
textEncoder: new TextEncoder(),
textDecoder: new TextDecoder()
});
});
const expectSuccessForICharactersSymbol = (i: number) => {
const symbol = genericValidSymbolCharacter.repeat(i);
const asset = `10.000 ${symbol}`;
serialBuffer.pushAsset(asset);
expect(serialBuffer.length).not.toBe(0);
};
const expectExceptionThrown = (asset: string) => {
let exceptionCaught = false;
try {
serialBuffer.pushAsset(asset);
} catch (e) {
expect(e.message).toBe(invalidSymbolErrorMessage);
exceptionCaught = true;
}
expect(exceptionCaught).toBeTruthy();
};
for (let i = 1; i <= 7; i++) {
it(`should be able to push asset with valid symbol of ${i} character(s)`, () => {
expectSuccessForICharactersSymbol(i);
});
}
it('should be able to push asset with valid ZSW symbol "10.000 ZSW"', () => {
const asset = '10.000 ZSW';
serialBuffer.pushAsset(asset);
expect(serialBuffer.length).not.toBe(0);
});
it('should not be able to push no symbol "10.000 "', () => {
const asset = '10.000 ';
expectExceptionThrown(asset);
});
it('should not be able to push symbol with 8 or more characters "10.000 AAAAAAAA"', () => {
const asset = '10.000 AAAAAAAA';
expectExceptionThrown(asset);
});
it('should not be able to push invalid lowercase symbol "10.000 zsw"', () => {
const asset = '10.000 zsw';
expectExceptionThrown(asset);
});
it('should not be able to push two symbols "10.000 ZSW blah"', () => {
const asset = '10.000 ZSW blah';
expectExceptionThrown(asset);
});
});
describe('name', () => {
let serialBuffer: SerialBuffer;
const invalidNameErrorMessage = 'Name should be less than 13 characters, or less than 14 if last character is between 1-5 or a-j, and only contain the following symbols .12345abcdefghijklmnopqrstuvwxyz';
beforeEach(() => {
serialBuffer = new SerialBuffer({
textEncoder: new TextEncoder(),
textDecoder: new TextDecoder()
});
});
it('should be able to push name with a valid account name', () => {
const name = '.12345abcdefg';
serialBuffer.pushName(name);
expect(serialBuffer.getName()).toEqual(name);
});
it('should remove the `.` character from the end of the account name', () => {
const name = 'abcd......';
const expectedName = 'abcd';
serialBuffer.pushName(name);
expect(serialBuffer.getName()).toEqual(expectedName);
});
it('should not be able to push name with an account name too long', () => {
const name = 'abcdabcdabcdab';
const shouldFail = () => serialBuffer.pushName(name);
expect(shouldFail).toThrowError(invalidNameErrorMessage);
});
it('should not be able to push name with an account name with invalid characters', () => {
const name = '6789$/,';
const shouldFail = () => serialBuffer.pushName(name);
expect(shouldFail).toThrowError(invalidNameErrorMessage);
});
});
describe('bool', () => {
let boolType: Type;
let mockedBuffer: SerialBuffer;
const shouldThrowErrorForValue = (value: any) => {
try {
boolType.serialize(mockedBuffer, value);
} catch (e) {
expect(e.message).toBe('Expected boolean or number equal to 1 or 0');
}
};
const shouldNotThrowErrorForValue = (value: any) => {
expect(() => {
boolType.serialize(mockedBuffer, value);
}).not.toThrow();
};
beforeAll(() => {
boolType = types.get('bool');
mockedBuffer = Object.create(SerialBuffer);
mockedBuffer.push = jest.fn().mockImplementation((value) => {
return;
});
});
it('should be able to create bool type', () => {
expect(boolType).toBeTruthy();
});
it('should throw error when calling serialize when type is not boolean or number', () => {
const dataValue = 'string';
shouldThrowErrorForValue(dataValue);
});
it('should throw error when calling serialize when number that is not 1 or 0', () => {
const dataValue = 10;
shouldThrowErrorForValue(dataValue);
});
it('should not throw error when calling serialize with false', () => {
const dataValue = false;
shouldNotThrowErrorForValue(dataValue);
});
it('should not throw error when calling serialize with true', () => {
const dataValue = true;
shouldNotThrowErrorForValue(dataValue);
});
it('should not throw error when calling serialize with 0', () => {
const dataValue = 0;
shouldNotThrowErrorForValue(dataValue);
});
it('should not throw error when calling serialize with 1', () => {
const dataValue = 1;
shouldNotThrowErrorForValue(dataValue);
});
});
});
import {PrivateKey, PublicKey, Signature} from './zswjs-jssig';
import {generateKeyPair} from './zswjs-key-conversions';
import {KeyType} from './zswjs-numeric';
import {ec as EC} from 'elliptic';
export const ecc = {
initialize: (): void => console.error('Method deprecated'),
unsafeRandomKey: (): void => console.error('Method deprecated'),
randomKey: (
cpuEntropyBits?: number, options: { secureEnv?: boolean, ecOptions?: EC.GenKeyPairOptions } = {}
): Promise<string> => {
if (cpuEntropyBits !== undefined) {
console.warn('Argument `cpuEntropyBits` is deprecated, ' +
'use the options argument instead');
}
const { privateKey } = generateKeyPair(KeyType.k1, options);
return Promise.resolve(privateKey.toLegacyString());
},
seedPrivate: (): void => console.error('Method deprecated'),
privateToPublic: (key: string, pubkey_prefix?: string): string => {
if (pubkey_prefix !== undefined) {
console.warn('Argument `pubkey_prefix` is deprecated, ' +
'keys prefixed with PUB_K1_/PUB_R1_/PUB_WA_ going forward');
}
const privateKey = PrivateKey.fromString(key);
const publicKey = privateKey.getPublicKey();
return publicKey.toLegacyString();
},
isValidPublic: (pubkey: string, pubkey_prefix?: string): boolean => {
if (pubkey_prefix !== undefined) {
console.warn('Argument `pubkey_prefix` is deprecated, ' +
'keys prefixed with PUB_K1_/PUB_R1_/PUB_WA_ going forward');
}
try {
const publicKey = PublicKey.fromString(pubkey);
return publicKey.isValid();
} catch {
return false;
}
},
isValidPrivate: (wif: string): boolean => {
try {
const privateKey = PrivateKey.fromString(wif);
return privateKey.isValid();
} catch {
return false;
}
},
sign: (data: string|Buffer, privateKey: string|PrivateKey, encoding: BufferEncoding = 'utf8'): string => {
const privKey = typeof privateKey === 'string' ? PrivateKey.fromString(privateKey) : privateKey;
const signature = privKey.sign(data, true, encoding);
return signature.toString();
},
signHash: (dataSha256: string|Buffer, privateKey: string|PrivateKey, encoding: BufferEncoding = 'hex'): string => {
const privKey = typeof privateKey === 'string' ? PrivateKey.fromString(privateKey) : privateKey;
const signature = privKey.sign(dataSha256, false, encoding);
return signature.toString();
},
verify: (
signature: string, data: string, pubKey: string|PublicKey, encoding: BufferEncoding = 'utf8', hashData: boolean = true
): boolean => {
const publicKey = typeof pubKey === 'string' ? PublicKey.fromString(pubKey) : pubKey;
const sig = Signature.fromString(signature);
return sig.verify(data, publicKey, hashData, encoding);
},
recover: (signature: string, data: string, encoding: BufferEncoding = 'utf8'): string => {
const sig = Signature.fromString(signature);
const publicKey = sig.recover(data, true, encoding);
return publicKey.toLegacyString();
},
recoverHash: (signature: string, dataSha256: string|Buffer, encoding: BufferEncoding = 'hex'): string => {
const sig = Signature.fromString(signature);
const publicKey = sig.recover(dataSha256, false, encoding);
return publicKey.toLegacyString();
},
sha256: (data: string|Buffer, resultEncoding?: string, encoding?: string): string|Buffer => {
if (encoding !== undefined) {
console.warn('Argument `encoding` is deprecated');
}
if (resultEncoding !== undefined) {
console.warn('Argument `resultEncoding` is deprecated');
}
return require('./zswjs-key-conversions').sha256(data);
}
};
/**
* @module Javascript-API
* copyright defined in zswjs/LICENSE.txt
*/
import { Abi, PushTransactionArgs, ProcessedAction } from './zswjs-rpc-interfaces';
import { Anyvar, Authorization, Action, SerializedAction } from './zswjs-serialize';
/** Arguments to `getRequiredKeys` */
export interface AuthorityProviderArgs {
/** Transaction that needs to be signed */
transaction: any;
/** Public keys associated with the private keys that the `SignatureProvider` holds */
availableKeys: string[];
}
/** Get subset of `availableKeys` needed to meet authorities in `transaction` */
export interface AuthorityProvider {
/** Get subset of `availableKeys` needed to meet authorities in `transaction` */
getRequiredKeys: (args: AuthorityProviderArgs) => Promise<string[]>;
}
/** Retrieves raw ABIs for a specified accountName */
export interface AbiProvider {
/** Retrieve the BinaryAbi */
getRawAbi: (accountName: string) => Promise<BinaryAbi>;
}
/** Structure for the raw form of ABIs */
export interface BinaryAbi {
/** account which has deployed the ABI */
accountName: string;
/** abi in binary form */
abi: Uint8Array;
}
/** Holds a fetched abi */
export interface CachedAbi {
/** abi in binary form */
rawAbi: Uint8Array;
/** abi in structured form */
abi: Abi;
}
/** Arguments to `sign` */
export interface SignatureProviderArgs {
/** Chain transaction is for */
chainId: string;
/** Public keys associated with the private keys needed to sign the transaction */
requiredKeys: string[];
/** Transaction to sign */
serializedTransaction: Uint8Array;
/** Context-free data to sign */
serializedContextFreeData?: Uint8Array;
/** ABIs for all contracts with actions included in `serializedTransaction` */
abis: BinaryAbi[];
}
/** Signs transactions */
export interface SignatureProvider {
/** Public keys associated with the private keys that the `SignatureProvider` holds */
getAvailableKeys: () => Promise<string[]>;
/** Sign a transaction */
sign: (args: SignatureProviderArgs) => Promise<PushTransactionArgs>;
}
export interface ResourcePayer {
payer: string;
max_net_bytes: number;
max_cpu_us: number;
max_memory_bytes: number;
}
export interface Transaction {
expiration?: string;
ref_block_num?: number;
ref_block_prefix?: number;
max_net_usage_words?: number;
max_cpu_usage_ms?: number;
delay_sec?: number;
context_free_actions?: Action[];
context_free_data?: Uint8Array[];
actions: Action[];
transaction_extensions?: [number, string][];
resource_payer?: ResourcePayer;
}
/** Optional transact configuration object */
export interface TransactConfig {
broadcast?: boolean;
sign?: boolean;
readOnlyTrx?: boolean;
returnFailureTraces?: boolean;
requiredKeys?: string[];
compression?: boolean;
blocksBehind?: number;
useLastIrreversible?: boolean;
expireSeconds?: number;
}
export interface TransactionHeader {
expiration: string;
ref_block_num: number;
ref_block_prefix: number;
}
export interface AccountDelta {
account: string;
delta: number;
}
export interface AuthSequence {
account: string;
sequence: number;
}
export interface ActionReceipt {
receiver: string;
act_digest: string;
global_sequence: number;
recv_sequence: number;
auth_sequence: [ string, number ][];
code_sequence: number;
abi_sequence: number;
}
export interface ActionTrace {
action_ordinal: number;
creator_action_ordinal: number;
closest_unnotified_ancestor_action_ordinal: number;
receipt: ActionReceipt;
receiver: string;
act: ProcessedAction;
context_free: boolean;
elapsed: number;
console: string;
trx_id: string;
block_num: number;
block_time: string;
producer_block_id: string|null;
account_ram_deltas: AccountDelta[];
account_disk_deltas: AccountDelta[];
except: any;
error_code: number|null;
return_value?: any;
return_value_hex_data?: string;
return_value_data?: any;
inline_traces?: ActionTrace[];
}
export interface TransactionReceiptHeader {
status: string;
cpu_usage_us: number;
net_usage_words: number;
}
export interface TransactionTrace {
id: string;
block_num: number;
block_time: string;
producer_block_id: string|null;
receipt: TransactionReceiptHeader|null;
elapsed: number;
net_usage: number;
scheduled: boolean;
action_traces: ActionTrace[];
account_ram_delta: AccountDelta|null;
except: string|null;
error_code: number|null;
bill_to_accounts: string[];
}
export interface TransactResult {
transaction_id: string;
processed: TransactionTrace;
}
/** Optional query configuration object */
export interface QueryConfig {
sign?: boolean;
requiredKeys?: string[];
authorization?: Authorization[];
}
/**
* A Query may be any of the following:
* * string: method
* * [string, Query[]]: [method, filter]
* * [string, Anyvar, Query[]]: [method, arg, filter]
* * {method: string, arg?: Anyvar, filter?: Query[]} explicit form
*/
export type Query =
string | [string, Query[]] | [string, Anyvar, Query[]] | { method: string, arg?: Anyvar, filter?: Query[] };
export type ContextFreeGroupCallback =
(index: {cfa: number, cfd: number}) => {
action?: SerializedAction;
contextFreeAction?: SerializedAction;
contextFreeData?: Uint8Array;
};
export interface ActionSerializerType {
[actionName: string]: any;
};
/**
* @module JS-Sig
*/
// copyright defined in zswjs/LICENSE.txt
import { ec } from 'elliptic';
import { SignatureProvider, SignatureProviderArgs } from './zswjs-api-interfaces';
import { PushTransactionArgs } from './zswjs-rpc-interfaces';
import {
PrivateKey,
PublicKey,
Signature,
} from './zswjs-key-conversions';
import { convertLegacyPublicKey } from './zswjs-numeric';
/** expensive to construct; so we do it once and reuse it */
const defaultEc = new ec('secp256k1');
/** Construct the digest from transaction details */
const digestFromSerializedData = (
chainId: string,
serializedTransaction: Uint8Array,
serializedContextFreeData?: Uint8Array,
e = defaultEc): string => {
const signBuf = Buffer.concat([
Buffer.from(chainId, 'hex'),
Buffer.from(serializedTransaction),
Buffer.from(
serializedContextFreeData ?
new Uint8Array(e.hash().update(serializedContextFreeData).digest()) :
new Uint8Array(32)
),
]);
return e.hash().update(signBuf).digest();
};
/** Signs transactions using in-process private keys */
class JsSignatureProvider implements SignatureProvider {
/** map public to private keys */
public keys = new Map<string, ec.KeyPair>();
/** public keys */
public availableKeys = [] as string[];
/** @param privateKeys private keys to sign with */
constructor(privateKeys: string[]) {
for (const k of privateKeys) {
const priv = PrivateKey.fromString(k);
const privElliptic = priv.toElliptic();
const pubStr = priv.getPublicKey().toString();
this.keys.set(pubStr, privElliptic);
this.availableKeys.push(pubStr);
}
}
/** Public keys associated with the private keys that the `SignatureProvider` holds */
public async getAvailableKeys(): Promise<string[]> {
return this.availableKeys;
}
/** Sign a transaction */
public async sign(
{ chainId, requiredKeys, serializedTransaction, serializedContextFreeData }: SignatureProviderArgs,
): Promise<PushTransactionArgs> {
const digest = digestFromSerializedData( chainId, serializedTransaction, serializedContextFreeData, defaultEc);
const signatures = [] as string[];
for (const key of requiredKeys) {
const publicKey = PublicKey.fromString(key);
const ellipticPrivateKey = this.keys.get(convertLegacyPublicKey(key));
const privateKey = PrivateKey.fromElliptic(ellipticPrivateKey, publicKey.getType());
const signature = privateKey.sign(digest, false);
signatures.push(signature.toString());
}
return { signatures, serializedTransaction, serializedContextFreeData };
}
}
export {
PrivateKey,
PublicKey,
Signature,
digestFromSerializedData,
JsSignatureProvider,
};
import {ec as EC} from 'elliptic';
import * as hash from 'hash.js';
import {KeyType} from './zswjs-numeric';
import { PublicKey } from './PublicKey';
import { PrivateKey } from './PrivateKey';
export { PrivateKey } from './PrivateKey';
export { PublicKey } from './PublicKey';
export { Signature } from './Signature';
/** Construct the elliptic curve object based on key type */
export const constructElliptic = (type: KeyType): EC => {
if (type === KeyType.k1) {
return new EC('secp256k1');
}
return new EC('p256');
};
export const generateKeyPair = (
type: KeyType, options: { secureEnv?: boolean, ecOptions?: EC.GenKeyPairOptions } = {}
): { publicKey: PublicKey, privateKey: PrivateKey } => {
if (!options.secureEnv) {
throw new Error('Key generation is completely INSECURE in production environments in the browser. ' +
'If you are absolutely certain this does NOT describe your environment, set `secureEnv` in your ' +
'options to `true`. If this does describe your environment and you set `secureEnv` to `true`, ' +
'YOU DO SO AT YOUR OWN RISK AND THE RISK OF YOUR USERS.');
}
let ec;
if (type === KeyType.k1) {
ec = new EC('secp256k1');
} else {
ec = new EC('p256');
}
const ellipticKeyPair = ec.genKeyPair(options.ecOptions);
const publicKey = PublicKey.fromElliptic(ellipticKeyPair, type, ec);
const privateKey = PrivateKey.fromElliptic(ellipticKeyPair, type, ec);
return {publicKey, privateKey};
};
export const sha256 = (data: string|Buffer): number[]|string => {
return hash.sha256().update(data).digest();
};
/**
* @module RPC-Error
*/
// copyright defined in zswjs/LICENSE.txt
/** Holds detailed error information */
export class RpcError extends Error {
/** Detailed error information */
public json: any;
public details: any;
constructor(json: any) {
if (json.error && json.error.details && json.error.details.length && json.error.details[0].message) {
super(json.error.details[0].message);
this.details = json.error.details;
} else if (json.processed && json.processed.except && json.processed.except.message) {
super(json.processed.except.message);
this.details = json.processed.except;
} else if (json.result && json.result.except && json.result.except.message) {
super(json.result.except.message);
this.details = json.result.except;
} else {
super(json.message);
}
Object.setPrototypeOf(this, RpcError.prototype);
this.json = json;
}
}
/**
* @module WebAuthn-Sig
*/
// copyright defined in zswjs/LICENSE.txt
import { SignatureProvider, SignatureProviderArgs } from './zswjs-api-interfaces';
import { PushTransactionArgs } from './zswjs-rpc-interfaces';
import * as ser from './zswjs-serialize';
import * as numeric from './zswjs-numeric';
import { ec } from 'elliptic';
/** Signs transactions using WebAuthn */
export class WebAuthnSignatureProvider implements SignatureProvider {
/** Map public key to credential ID (hex). User must populate this. */
public keys = new Map<string, string>();
/** Public keys that the `SignatureProvider` holds */
public async getAvailableKeys(): Promise<string[]> {
return Array.from(this.keys.keys());
}
/** Sign a transaction */
public async sign(
{ chainId, requiredKeys, serializedTransaction, serializedContextFreeData }: SignatureProviderArgs,
): Promise<PushTransactionArgs> {
const signBuf = new ser.SerialBuffer();
signBuf.pushArray(ser.hexToUint8Array(chainId));
signBuf.pushArray(serializedTransaction);
if (serializedContextFreeData) {
signBuf.pushArray(new Uint8Array(await crypto.subtle.digest('SHA-256', serializedContextFreeData.buffer)));
} else {
signBuf.pushArray(new Uint8Array(32));
}
const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', signBuf.asUint8Array().slice().buffer));
const signatures = [] as string[];
for (const key of requiredKeys) {
const id = ser.hexToUint8Array(this.keys.get(key));
const assertion = await (navigator as any).credentials.get({
publicKey: {
timeout: 60000,
allowCredentials: [{
id,
type: 'public-key',
}],
challenge: digest.buffer,
},
});
const e = new ec('p256') as any; // https://github.com/indutny/elliptic/pull/232
const pubKey = e.keyFromPublic(numeric.stringToPublicKey(key).data.subarray(0, 33)).getPublic();
const fixup = (x: Uint8Array): Uint8Array => {
const a = Array.from(x);
while (a.length < 32) {
a.unshift(0);
}
while (a.length > 32) {
if (a.shift() !== 0) {
throw new Error('Signature has an r or s that is too big');
}
}
return new Uint8Array(a);
};
const der = new ser.SerialBuffer({ array: new Uint8Array(assertion.response.signature) });
if (der.get() !== 0x30) {
throw new Error('Signature missing DER prefix');
}
if (der.get() !== der.array.length - 2) {
throw new Error('Signature has bad length');
}
if (der.get() !== 0x02) {
throw new Error('Signature has bad r marker');
}
const r = fixup(der.getUint8Array(der.get()));
if (der.get() !== 0x02) {
throw new Error('Signature has bad s marker');
}
const s = fixup(der.getUint8Array(der.get()));
const whatItReallySigned = new ser.SerialBuffer();
whatItReallySigned.pushArray(new Uint8Array(assertion.response.authenticatorData));
whatItReallySigned.pushArray(new Uint8Array(
await crypto.subtle.digest('SHA-256', assertion.response.clientDataJSON)));
const hash = new Uint8Array(
await crypto.subtle.digest('SHA-256', whatItReallySigned.asUint8Array().slice()));
const recid = e.getKeyRecoveryParam(hash, new Uint8Array(assertion.response.signature), pubKey);
const sigData = new ser.SerialBuffer();
sigData.push(recid + 27 + 4);
sigData.pushArray(r);
sigData.pushArray(s);
sigData.pushBytes(new Uint8Array(assertion.response.authenticatorData));
sigData.pushBytes(new Uint8Array(assertion.response.clientDataJSON));
const sig = numeric.signatureToString({
type: numeric.KeyType.wa,
data: sigData.asUint8Array().slice(),
});
signatures.push(sig);
}
return { signatures, serializedTransaction, serializedContextFreeData };
}
}
{
"compilerOptions": {
"target": "es5",
"module": "CommonJS",
"outDir": "dist",
"alwaysStrict": true,
"sourceMap": true,
"noImplicitAny": true,
"moduleResolution": "node",
"declaration": true,
"downlevelIteration": true,
"skipLibCheck": true,
"lib": [
"es2017",
"dom"
]
},
"include": [
"src/**/*.ts",
"src/**/*.js"
]
}
{
"compilerOptions": {
"target": "es5",
"module": "CommonJS",
"outDir": "dist",
"alwaysStrict": true,
"sourceMap": false,
"noImplicitAny": true,
"moduleResolution": "node",
"declaration": false,
"downlevelIteration": true,
"lib": [
"es2017",
"dom"
]
},
"include": [
"src/**/*.ts",
"src/**/*.js"
],
"exclude": [
"src/**/*.test.ts",
"src/tests/"
]
}
{
"tsconfig": "tsconfig.json",
"ignoreCompilerErrors": true,
"hideGenerator": true,
"includeDeclarations": false,
"excludeExternals": true,
"excludePrivate": true,
"excludeProtected": true,
"externalPattern": "**/node_modules/**",
"excludeNotExported": true,
"exclude": ["**/index*","**/*.test.ts","**/*.test.js", "**/node_modules/**"],
"mode": "modules",
"readme": "README.md",
"module": "commonjs",
"theme": "minimal",
"target": "ES5",
"out": "./reference"
}
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
zswjs_api: './src/zswjs-api.ts',
zswjs_jsonrpc: './src/rpc-web.ts',
zswjs_jssig: './src/zswjs-jssig.ts',
zswjs_numeric: './src/zswjs-numeric.ts',
},
devtool: 'inline-source-map',
mode: 'development',
module: {
rules: [
{
test: /\.tsx?$/,
use: {
loader: 'ts-loader',
options: {
configFile: 'tsconfig.web.json'
}
},
exclude: /node_modules/,
}
]
},
plugins: [
new webpack.ProvidePlugin({
Buffer: ["buffer", "Buffer"],
})
],
resolve: {
extensions: ['.tsx', '.ts', '.js'],
fallback: {
buffer: 'buffer',
crypto: 'crypto-browserify'
}
},
output: {
filename: x => x.chunk.name.replace('_', '-') + '.js',
library: '[name]',
path: path.resolve(__dirname, 'dist-web'),
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'externals',
filename: 'externals.js',
chunks: 'all'
},
},
},
}
};
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');
module.exports = {
entry: {
zswjs_api: './src/zswjs-api.ts',
zswjs_jsonrpc: './src/rpc-web.ts',
zswjs_jssig: './src/zswjs-jssig.ts',
zswjs_numeric: './src/zswjs-numeric.ts',
},
mode: 'production',
module: {
rules: [
{
test: /\.tsx?$/,
use: {
loader: 'ts-loader',
options: {
configFile: 'tsconfig.web.json'
}
},
exclude: /node_modules/,
}
]
},
plugins: [
new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: ['**/*'] }),
new webpack.ProvidePlugin({
Buffer: ["buffer", "Buffer"],
})
],
resolve: {
extensions: ['.tsx', '.ts', '.js'],
fallback: {
buffer: 'buffer',
crypto: 'crypto-browserify'
}
},
output: {
filename: x => x.chunk.name.replace('_', '-') + '.min.js',
library: '[name]',
path: path.resolve(__dirname, 'dist-web'),
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'externals',
filename: 'externals.min.js',
chunks: 'all'
},
},
},
}
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment