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 };
// https://gist.githubusercontent.com/wlzla000/bac83df6d3c51916c4dd0bc947e46947/raw/7ee3462b095ab22580ddaf191f44a590da6fe33b/RIPEMD-160.js
/*
RIPEMD-160.js
developed
by K. (https://github.com/wlzla000)
on December 27-29, 2017,
licensed under
the MIT license
Copyright (c) 2017 K.
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.
*/
/* eslint-disable */
"use strict";
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var RIPEMD160 = function () {
function RIPEMD160() {
// https://webcache.googleusercontent.com/search?q=cache:CnLOgolTHYEJ:https://www.cosic.esat.kuleuven.be/publications/article-317.pdf
// http://shodhganga.inflibnet.ac.in/bitstream/10603/22978/13/13_appendix.pdf
_classCallCheck(this, RIPEMD160);
}
_createClass(RIPEMD160, null, [{
key: "get_n_pad_bytes",
value: function get_n_pad_bytes(message_size /* in bytes, 1 byte is 8 bits. */) {
// Obtain the number of bytes needed to pad the message.
// It does not contain the size of the message size information.
/*
https://webcache.googleusercontent.com/search?q=cache:CnLOgolTHYEJ:https://www.cosic.esat.kuleuven.be/publications/article-317.pdf
The Cryptographic Hash Function RIPEMD-160
written by
Bart Preneel,
Hans Dobbertin,
Antoon Bosselaers
in
1997.
--------------------------------------------------
§5 Description of RIPEMD-160
......
In order to guarantee that the total input size is a
multiple of 512 bits, the input is padded in the same
way as for all the members of the MD4-family: one
appends a single 1 followed by a string of 0s (the
number of 0s lies between 0 and 511); the last 64 bits
of the extended input contain the binary representation
of the input size in bits, least significant byte first.
*/
/*
https://tools.ietf.org/rfc/rfc1186.txt
RFC 1186: MD4 Message Digest Algorithm.
written by
Ronald Linn Rivest
in
October 1990.
--------------------------------------------------
§3 MD4 Algorithm Description
......
Step 1. Append padding bits
The message is "padded" (extended) so that its length
(in bits) is congruent to 448, modulo 512. That is, the
message is extended so that it is just 64 bits shy of
being a multiple of 512 bits long. Padding is always
performed, even if the length of the message is already
congruent to 448, modulo 512 (in which case 512 bits of
padding are added).
Padding is performed as follows: a single "1" bit is
appended to the message, and then enough zero bits are
appended so that the length in bits of the padded
message becomes congruent to 448, modulo 512.
Step 2. Append length
A 64-bit representation of b (the length of the message
before the padding bits were added) is appended to the
result of the previous step. In the unlikely event that
b is greater than 2^64, then only the low-order 64 bits
of b are used. (These bits are appended as two 32-bit
words and appended low-order word first in accordance
with the previous conventions.)
At this point the resulting message (after padding with
bits and with b) has a length that is an exact multiple
of 512 bits. Equivalently, this message has a length
that is an exact multiple of 16 (32-bit) words. Let
M[0 ... N-1] denote the words of the resulting message,
where N is a multiple of 16.
*/
// https://crypto.stackexchange.com/a/32407/54568
/*
Example case # 1
[0 bit: message.]
[1 bit: 1.]
[447 bits: 0.]
[64 bits: message size information.]
Example case # 2
[512-bits: message]
[1 bit: 1.]
[447 bits: 0.]
[64 bits: message size information.]
Example case # 3
[(512 - 64 = 448) bits: message.]
[1 bit: 1.]
[511 bits: 0.]
[64 bits: message size information.]
Example case # 4
[(512 - 65 = 447) bits: message.]
[1 bit: 1.]
[0 bit: 0.]
[64 bits: message size information.]
*/
// The number of padding zero bits:
// 511 - [{(message size in bits) + 64} (mod 512)]
return 64 - (message_size + 8 & 63 /* 63 */);
}
}, {
key: "pad",
value: function pad(message /* An ArrayBuffer. */) {
var message_size = message.byteLength;
var n_pad = RIPEMD160.get_n_pad_bytes(message_size);
// `Number.MAX_SAFE_INTEGER` is ((2 ** 53) - 1) and
// bitwise operation in Javascript is done on 32-bits operands.
var divmod = function divmod(dividend, divisor) {
return [Math.floor(dividend / divisor), dividend % divisor];
};
/*
To shift
00000000 000????? ???????? ???????? ???????? ???????? ???????? ????????
t o
00000000 ???????? ???????? ???????? ???????? ???????? ???????? ?????000
--------------------------------------------------------------------------------
Method #1
00000000 000????? ???????? ???????? ???????? ???????? ???????? ????????
[00000000 000AAAAA AAAAAAAA AAAAAAAA] (<A> captured)
[00000000 AAAAAAAA AAAAAAAA AAAAA000] (<A> shifted)
(<B> captured) [BBBBBBBB BBBBBBBB BBBBBBBB BBBBBBBB]
(<B> shifted) [BBB][BBBBBBBB BBBBBBBB BBBBBBBB BBBBB000]
[00000000 AAAAAAAA AAAAAAAA AAAAABBB] (<A> & <B_2> merged)
[00000000 AAAAAAAA AAAAAAAA AAAAABBB][BBBBBBBB BBBBBBBB BBBBBBBB BBBBB000]
00000000 ???????? ???????? ???????? ???????? ???????? ???????? ?????000
const uint32_max_plus_1 = 0x100000000; // (2 ** 32)
const [
msg_byte_size_most, // Value range [0, (2 ** 21) - 1].
msg_byte_size_least // Value range [0, (2 ** 32) - 1].
] = divmod(message_size, uint32_max_plus_1);
const [
carry, // Value range [0, 7].
msg_bit_size_least // Value range [0, (2 ** 32) - 8].
] = divmod(message_byte_size_least * 8, uint32_max_plus_1);
const message_bit_size_most = message_byte_size_most * 8
+ carry; // Value range [0, (2 ** 24) - 1].
--------------------------------------------------------------------------------
Method #2
00000000 000????? ???????? ???????? ???????? ???????? ???????? ????????
[00000 000AAAAA AAAAAAAA AAAAAAAA AAA] (<A> captured)
(<B> captured) [000BBBBB BBBBBBBB BBBBBBBB BBBBBBBB]
(<B> shifted) [BBBBBBBB BBBBBBBB BBBBBBBB BBBBB000]
[00000000 AAAAAAAA AAAAAAAA AAAAAAAA][BBBBBBBB BBBBBBBB BBBBBBBB BBBBB000]
00000000 ???????? ???????? ???????? ???????? ???????? ???????? ?????000
*/
var _divmod$map = divmod(message_size, 536870912 /* (2 ** 29) */).map(function (x, index) {
return index ? x * 8 : x;
}),
_divmod$map2 = _slicedToArray(_divmod$map, 2),
msg_bit_size_most = _divmod$map2[0],
msg_bit_size_least = _divmod$map2[1];
// `ArrayBuffer.transfer()` is not supported.
var padded = new Uint8Array(message_size + n_pad + 8);
padded.set(new Uint8Array(message), 0);
var data_view = new DataView(padded.buffer);
data_view.setUint8(message_size, 128);
data_view.setUint32(message_size + n_pad, msg_bit_size_least, true // Little-endian
);
data_view.setUint32(message_size + n_pad + 4, msg_bit_size_most, true // Little-endian
);
return padded.buffer;
}
}, {
key: "f",
value: function f(j, x, y, z) {
if (0 <= j && j <= 15) {
// Exclusive-OR
return x ^ y ^ z;
}
if (16 <= j && j <= 31) {
// Multiplexing (muxing)
return x & y | ~x & z;
}
if (32 <= j && j <= 47) {
return (x | ~y) ^ z;
}
if (48 <= j && j <= 63) {
// Multiplexing (muxing)
return x & z | y & ~z;
}
if (64 <= j && j <= 79) {
return x ^ (y | ~z);
}
}
}, {
key: "K",
value: function K(j) {
if (0 <= j && j <= 15) {
return 0x00000000;
}
if (16 <= j && j <= 31) {
// Math.floor((2 ** 30) * Math.SQRT2)
return 0x5A827999;
}
if (32 <= j && j <= 47) {
// Math.floor((2 ** 30) * Math.sqrt(3))
return 0x6ED9EBA1;
}
if (48 <= j && j <= 63) {
// Math.floor((2 ** 30) * Math.sqrt(5))
return 0x8F1BBCDC;
}
if (64 <= j && j <= 79) {
// Math.floor((2 ** 30) * Math.sqrt(7))
return 0xA953FD4E;
}
}
}, {
key: "KP",
value: function KP(j) // K'
{
if (0 <= j && j <= 15) {
// Math.floor((2 ** 30) * Math.cbrt(2))
return 0x50A28BE6;
}
if (16 <= j && j <= 31) {
// Math.floor((2 ** 30) * Math.cbrt(3))
return 0x5C4DD124;
}
if (32 <= j && j <= 47) {
// Math.floor((2 ** 30) * Math.cbrt(5))
return 0x6D703EF3;
}
if (48 <= j && j <= 63) {
// Math.floor((2 ** 30) * Math.cbrt(7))
return 0x7A6D76E9;
}
if (64 <= j && j <= 79) {
return 0x00000000;
}
}
}, {
key: "add_modulo32",
value: function add_modulo32() /* ...... */{
// 1. Modulo addition (addition modulo) is associative.
// https://proofwiki.org/wiki/Modulo_Addition_is_Associative
// 2. Bitwise operation in Javascript
// is done on 32-bits operands
// and results in a 32-bits value.
return Array.from(arguments).reduce(function (a, b) {
return a + b;
}, 0) | 0;
}
}, {
key: "rol32",
value: function rol32(value, count) {
// Cyclic left shift (rotate) on 32-bits value.
return value << count | value >>> 32 - count;
}
}, {
key: "hash",
value: function hash(message /* An ArrayBuffer. */) {
////////// Padding //////////
// The padded message.
var padded = RIPEMD160.pad(message);
////////// Compression //////////
// Message word selectors.
var r = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13];
var rP = [// r'
5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11];
// Amounts for 'rotate left' operation.
var s = [11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6];
var sP = [// s'
8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11];
// The size, in bytes, of a word.
var word_size = 4;
// The size, in bytes, of a 16-words block.
var block_size = 64;
// The number of the 16-words blocks.
var t = padded.byteLength / block_size;
// The message after padding consists of t 16-word blocks that
// are denoted with X_i[j], with 0≤i≤(t − 1) and 0≤j≤15.
var X = new Array(t).fill(undefined).map(function (_, i) {
return function (j) {
return new DataView(padded, i * block_size, block_size).getUint32(j * word_size, true // Little-endian
);
};
});
// The result of RIPEMD-160 is contained in five 32-bit words,
// which form the internal state of the algorithm. The final
// content of these five 32-bit words is converted to a 160-bit
// string, again using the little-endian convention.
var h = [0x67452301, // h_0
0xEFCDAB89, // h_1
0x98BADCFE, // h_2
0x10325476, // h_3
0xC3D2E1F0 // h_4
];
for (var i = 0; i < t; ++i) {
var A = h[0],
B = h[1],
C = h[2],
D = h[3],
E = h[4];
var AP = A,
BP = B,
CP = C,
DP = D,
EP = E;
for (var j = 0; j < 80; ++j) {
// Left rounds
var _T = RIPEMD160.add_modulo32(RIPEMD160.rol32(RIPEMD160.add_modulo32(A, RIPEMD160.f(j, B, C, D), X[i](r[j]), RIPEMD160.K(j)), s[j]), E);
A = E;
E = D;
D = RIPEMD160.rol32(C, 10);
C = B;
B = _T;
// Right rounds
_T = RIPEMD160.add_modulo32(RIPEMD160.rol32(RIPEMD160.add_modulo32(AP, RIPEMD160.f(79 - j, BP, CP, DP), X[i](rP[j]), RIPEMD160.KP(j)), sP[j]), EP);
AP = EP;
EP = DP;
DP = RIPEMD160.rol32(CP, 10);
CP = BP;
BP = _T;
}
var T = RIPEMD160.add_modulo32(h[1], C, DP);
h[1] = RIPEMD160.add_modulo32(h[2], D, EP);
h[2] = RIPEMD160.add_modulo32(h[3], E, AP);
h[3] = RIPEMD160.add_modulo32(h[4], A, BP);
h[4] = RIPEMD160.add_modulo32(h[0], B, CP);
h[0] = T;
}
// The final output string then consists of the concatenatation
// of h_0, h_1, h_2, h_3, and h_4 after converting each h_i to a
// 4-byte string using the little-endian convention.
var result = new ArrayBuffer(20);
var data_view = new DataView(result);
h.forEach(function (h_i, i) {
return data_view.setUint32(i * 4, h_i, true);
});
return result;
}
}]);
return RIPEMD160;
}();
module.exports = {
RIPEMD160: RIPEMD160
};
// https://gist.githubusercontent.com/wlzla000/bac83df6d3c51916c4dd0bc947e46947/raw/7ee3462b095ab22580ddaf191f44a590da6fe33b/RIPEMD-160.js
/*
RIPEMD-160.js
developed
by K. (https://github.com/wlzla000)
on December 27-29, 2017,
licensed under
the MIT license
Copyright (c) 2017 K.
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.
*/
'use strict';
class RIPEMD160
{
constructor()
{
// https://webcache.googleusercontent.com/search?q=cache:CnLOgolTHYEJ:https://www.cosic.esat.kuleuven.be/publications/article-317.pdf
// http://shodhganga.inflibnet.ac.in/bitstream/10603/22978/13/13_appendix.pdf
}
static get_n_pad_bytes(message_size /* in bytes, 1 byte is 8 bits. */)
{
// Obtain the number of bytes needed to pad the message.
// It does not contain the size of the message size information.
/*
https://webcache.googleusercontent.com/search?q=cache:CnLOgolTHYEJ:https://www.cosic.esat.kuleuven.be/publications/article-317.pdf
The Cryptographic Hash Function RIPEMD-160
written by
Bart Preneel,
Hans Dobbertin,
Antoon Bosselaers
in
1997.
--------------------------------------------------
§5 Description of RIPEMD-160
......
In order to guarantee that the total input size is a
multiple of 512 bits, the input is padded in the same
way as for all the members of the MD4-family: one
appends a single 1 followed by a string of 0s (the
number of 0s lies between 0 and 511); the last 64 bits
of the extended input contain the binary representation
of the input size in bits, least significant byte first.
*/
/*
https://tools.ietf.org/rfc/rfc1186.txt
RFC 1186: MD4 Message Digest Algorithm.
written by
Ronald Linn Rivest
in
October 1990.
--------------------------------------------------
§3 MD4 Algorithm Description
......
Step 1. Append padding bits
The message is "padded" (extended) so that its length
(in bits) is congruent to 448, modulo 512. That is, the
message is extended so that it is just 64 bits shy of
being a multiple of 512 bits long. Padding is always
performed, even if the length of the message is already
congruent to 448, modulo 512 (in which case 512 bits of
padding are added).
Padding is performed as follows: a single "1" bit is
appended to the message, and then enough zero bits are
appended so that the length in bits of the padded
message becomes congruent to 448, modulo 512.
Step 2. Append length
A 64-bit representation of b (the length of the message
before the padding bits were added) is appended to the
result of the previous step. In the unlikely event that
b is greater than 2^64, then only the low-order 64 bits
of b are used. (These bits are appended as two 32-bit
words and appended low-order word first in accordance
with the previous conventions.)
At this point the resulting message (after padding with
bits and with b) has a length that is an exact multiple
of 512 bits. Equivalently, this message has a length
that is an exact multiple of 16 (32-bit) words. Let
M[0 ... N-1] denote the words of the resulting message,
where N is a multiple of 16.
*/
// https://crypto.stackexchange.com/a/32407/54568
/*
Example case # 1
[0 bit: message.]
[1 bit: 1.]
[447 bits: 0.]
[64 bits: message size information.]
Example case # 2
[512-bits: message]
[1 bit: 1.]
[447 bits: 0.]
[64 bits: message size information.]
Example case # 3
[(512 - 64 = 448) bits: message.]
[1 bit: 1.]
[511 bits: 0.]
[64 bits: message size information.]
Example case # 4
[(512 - 65 = 447) bits: message.]
[1 bit: 1.]
[0 bit: 0.]
[64 bits: message size information.]
*/
// The number of padding zero bits:
// 511 - [{(message size in bits) + 64} (mod 512)]
return 64 - ((message_size + 8) & 0b00111111 /* 63 */);
}
static pad(message /* An ArrayBuffer. */)
{
const message_size = message.byteLength;
const n_pad = RIPEMD160.get_n_pad_bytes(message_size);
// `Number.MAX_SAFE_INTEGER` is ((2 ** 53) - 1) and
// bitwise operation in Javascript is done on 32-bits operands.
const divmod = (dividend, divisor) => [
Math.floor(dividend / divisor),
dividend % divisor
];
/*
To shift
00000000 000????? ???????? ???????? ???????? ???????? ???????? ????????
t o
00000000 ???????? ???????? ???????? ???????? ???????? ???????? ?????000
--------------------------------------------------------------------------------
Method #1
00000000 000????? ???????? ???????? ???????? ???????? ???????? ????????
[00000000 000AAAAA AAAAAAAA AAAAAAAA] (<A> captured)
[00000000 AAAAAAAA AAAAAAAA AAAAA000] (<A> shifted)
(<B> captured) [BBBBBBBB BBBBBBBB BBBBBBBB BBBBBBBB]
(<B> shifted) [BBB][BBBBBBBB BBBBBBBB BBBBBBBB BBBBB000]
[00000000 AAAAAAAA AAAAAAAA AAAAABBB] (<A> & <B_2> merged)
[00000000 AAAAAAAA AAAAAAAA AAAAABBB][BBBBBBBB BBBBBBBB BBBBBBBB BBBBB000]
00000000 ???????? ???????? ???????? ???????? ???????? ???????? ?????000
const uint32_max_plus_1 = 0x100000000; // (2 ** 32)
const [
msg_byte_size_most, // Value range [0, (2 ** 21) - 1].
msg_byte_size_least // Value range [0, (2 ** 32) - 1].
] = divmod(message_size, uint32_max_plus_1);
const [
carry, // Value range [0, 7].
msg_bit_size_least // Value range [0, (2 ** 32) - 8].
] = divmod(message_byte_size_least * 8, uint32_max_plus_1);
const message_bit_size_most = message_byte_size_most * 8
+ carry; // Value range [0, (2 ** 24) - 1].
--------------------------------------------------------------------------------
Method #2
00000000 000????? ???????? ???????? ???????? ???????? ???????? ????????
[00000 000AAAAA AAAAAAAA AAAAAAAA AAA] (<A> captured)
(<B> captured) [000BBBBB BBBBBBBB BBBBBBBB BBBBBBBB]
(<B> shifted) [BBBBBBBB BBBBBBBB BBBBBBBB BBBBB000]
[00000000 AAAAAAAA AAAAAAAA AAAAAAAA][BBBBBBBB BBBBBBBB BBBBBBBB BBBBB000]
00000000 ???????? ???????? ???????? ???????? ???????? ???????? ?????000
*/
const [
msg_bit_size_most,
msg_bit_size_least
] = divmod(message_size, 536870912 /* (2 ** 29) */)
.map((x, index) => (index ? (x * 8) : x));
// `ArrayBuffer.transfer()` is not supported.
const padded = new Uint8Array(message_size + n_pad + 8);
padded.set(new Uint8Array(message), 0);
const data_view = new DataView(padded.buffer);
data_view.setUint8(message_size, 0b10000000);
data_view.setUint32(
message_size + n_pad,
msg_bit_size_least,
true // Little-endian
);
data_view.setUint32(
message_size + n_pad + 4,
msg_bit_size_most,
true // Little-endian
);
return padded.buffer;
}
static f(j, x, y, z)
{
if(0 <= j && j <= 15)
{ // Exclusive-OR
return x ^ y ^ z;
}
if(16 <= j && j <= 31)
{ // Multiplexing (muxing)
return (x & y) | (~x & z);
}
if(32 <= j && j <= 47)
{
return (x | ~y) ^ z;
}
if(48 <= j && j <= 63)
{ // Multiplexing (muxing)
return (x & z) | (y & ~z);
}
if(64 <= j && j <= 79)
{
return x ^ (y | ~z);
}
}
static K(j)
{
if(0 <= j && j <= 15)
{
return 0x00000000;
}
if(16 <= j && j <= 31)
{
// Math.floor((2 ** 30) * Math.SQRT2)
return 0x5A827999;
}
if(32 <= j && j <= 47)
{
// Math.floor((2 ** 30) * Math.sqrt(3))
return 0x6ED9EBA1;
}
if(48 <= j && j <= 63)
{
// Math.floor((2 ** 30) * Math.sqrt(5))
return 0x8F1BBCDC;
}
if(64 <= j && j <= 79)
{
// Math.floor((2 ** 30) * Math.sqrt(7))
return 0xA953FD4E;
}
}
static KP(j) // K'
{
if(0 <= j && j <= 15)
{
// Math.floor((2 ** 30) * Math.cbrt(2))
return 0x50A28BE6;
}
if(16 <= j && j <= 31)
{
// Math.floor((2 ** 30) * Math.cbrt(3))
return 0x5C4DD124;
}
if(32 <= j && j <= 47)
{
// Math.floor((2 ** 30) * Math.cbrt(5))
return 0x6D703EF3;
}
if(48 <= j && j <= 63)
{
// Math.floor((2 ** 30) * Math.cbrt(7))
return 0x7A6D76E9;
}
if(64 <= j && j <= 79)
{
return 0x00000000;
}
}
static add_modulo32(/* ...... */)
{
// 1. Modulo addition (addition modulo) is associative.
// https://proofwiki.org/wiki/Modulo_Addition_is_Associative
// 2. Bitwise operation in Javascript
// is done on 32-bits operands
// and results in a 32-bits value.
return Array
.from(arguments)
.reduce((a, b) => (a + b), 0) | 0;
}
static rol32(value, count)
{ // Cyclic left shift (rotate) on 32-bits value.
return (value << count) | (value >>> (32 - count));
}
static hash(message /* An ArrayBuffer. */)
{
// //////// Padding //////////
// The padded message.
const padded = RIPEMD160.pad(message);
// //////// Compression //////////
// Message word selectors.
const r = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13
];
const rP = [ // r'
5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11
];
// Amounts for 'rotate left' operation.
const s = [
11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6
];
const sP = [ // s'
8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11
];
// The size, in bytes, of a word.
const word_size = 4;
// The size, in bytes, of a 16-words block.
const block_size = 64;
// The number of the 16-words blocks.
const t = padded.byteLength / block_size;
// The message after padding consists of t 16-word blocks that
// are denoted with X_i[j], with 0≤i≤(t − 1) and 0≤j≤15.
const X = (new Array(t))
.fill(undefined)
.map((_, i) => j => (
new DataView(
padded, i * block_size, block_size
).getUint32(
j * word_size,
true // Little-endian
)
));
// The result of RIPEMD-160 is contained in five 32-bit words,
// which form the internal state of the algorithm. The final
// content of these five 32-bit words is converted to a 160-bit
// string, again using the little-endian convention.
const h = [
0x67452301, // h_0
0xEFCDAB89, // h_1
0x98BADCFE, // h_2
0x10325476, // h_3
0xC3D2E1F0 // h_4
];
for(let i = 0; i < t; ++i)
{
let A = h[0]; let B = h[1]; let C = h[2]; let D = h[3]; let E = h[4];
let AP = A; let BP = B; let CP = C; let DP = D; let EP = E;
for(let j = 0; j < 80; ++j)
{
// Left rounds
let T = RIPEMD160.add_modulo32( // eslint-disable-line no-shadow
RIPEMD160.rol32(
RIPEMD160.add_modulo32(
A,
RIPEMD160.f(j, B, C, D),
X[i](r[j]),
RIPEMD160.K(j)
),
s[j]
),
E
);
A = E;
E = D;
D = RIPEMD160.rol32(C, 10);
C = B;
B = T;
// Right rounds
T = RIPEMD160.add_modulo32(
RIPEMD160.rol32(
RIPEMD160.add_modulo32(
AP,
RIPEMD160.f(
79 - j,
BP,
CP,
DP
),
X[i](rP[j]),
RIPEMD160.KP(j)
),
sP[j]
),
EP
);
AP = EP;
EP = DP;
DP = RIPEMD160.rol32(CP, 10);
CP = BP;
BP = T;
}
const T = RIPEMD160.add_modulo32(h[1], C, DP);
h[1] = RIPEMD160.add_modulo32(h[2], D, EP);
h[2] = RIPEMD160.add_modulo32(h[3], E, AP);
h[3] = RIPEMD160.add_modulo32(h[4], A, BP);
h[4] = RIPEMD160.add_modulo32(h[0], B, CP);
h[0] = T;
}
// The final output string then consists of the concatenatation
// of h_0, h_1, h_2, h_3, and h_4 after converting each h_i to a
// 4-byte string using the little-endian convention.
const result = new ArrayBuffer(20);
const data_view = new DataView(result);
h.forEach((h_i, i) => data_view.setUint32(i * 4, h_i, true));
return result;
}
}
module.exports = {
RIPEMD160
};
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');
import { JsonRpc } from '../zswjs-jsonrpc';
import { JsSignatureProvider } from '../zswjs-jssig';
import { Api } from '../zswjs-api';
import * as ser from '../zswjs-serialize';
import fetch from 'node-fetch';
const { TextEncoder, TextDecoder } = require('util');
import {
AbiJsonToBinResult,
GetAbiResult,
GetAccountResult,
GetAccountsByAuthorizersResult,
GetActivatedProtocolFeaturesResult,
GetBlockHeaderStateResult,
GetBlockInfoResult,
GetBlockResult,
GetCodeResult,
GetCodeHashResult,
GetCurrencyStatsResult,
GetInfoResult,
GetProducerScheduleResult,
GetProducersResult,
GetRawCodeAndAbiResult,
GetRawAbiResult,
GetScheduledTransactionsResult,
GetTableRowsResult,
GetTableByScopeResult,
PushTransactionArgs,
ReadOnlyTransactResult,
AbiBinToJsonResult,
TraceApiGetBlockResult,
DBSizeGetResult,
} from '../zswjs-rpc-interfaces';
import { Transaction, TransactResult } from '../zswjs-api-interfaces';
import 'jest-extended';
const privateKey = '5JuH9fCXmU3xbj8nRmhPZaVrxxXrdPaRmZLW1cznNTmTQR2Kg5Z';
const rpc = new JsonRpc('http://localhost:8888', { fetch });
const signatureProvider = new JsSignatureProvider([privateKey]);
const api = new Api({ rpc, signatureProvider, textDecoder: new TextDecoder(), textEncoder: new TextEncoder() });
/** Checking types with verifyType/complexOrPrimitive
* To ensure that the data structure coming from zsw matches the declared types in zswjs for developers and documentation
* Since typescript is not a runtime language, it's required to test with javascript format
* Create an object matching the typescript type with some requirements:
* nullable: make the key a string and add a `&` character to the end
* optional: make the key a string and add a `?` character to the end (same as typescript)
* []: remove array symbols from simple/complex types, use arrays for std::pair
* Map<>: use Map<> in the value field
* |: operates the same as typescript but does not work for complex types
*/
describe('Chain API Plugin Endpoints', () => {
it('type check tests not required for this suite, see official type check tests',async()=>{
expect(1).toBe(1);
})
/*
it('validates return type of abi_bin_to_json', async () => {
const result: AbiBinToJsonResult = await rpc.abi_bin_to_json('returnvalue', 'sum', '0500000005000000');
const abiBinToJsonResult: any = {
args: 'any'
};
verifyType(result, abiBinToJsonResult);
});
it('validates return type of abi_json_to_bin', async () => {
const result: AbiJsonToBinResult = await rpc.abi_json_to_bin('returnvalue', 'sum', [5, 5]);
const abiJsonToBinResult: any = {
binargs: 'string'
};
verifyType(result, abiJsonToBinResult);
});
it('validates return type of get_abi', async () => {
const result: GetAbiResult = await rpc.get_abi('todo');
const getAbiResult: any = {
account_name: 'string',
'abi?': {
version: 'string',
types: {
new_type_name: 'string',
type: 'string',
},
structs: {
name: 'string',
base: 'string',
fields: {
name: 'string',
type: 'string',
},
},
actions: {
name: 'string',
type: 'string',
ricardian_contract: 'string',
},
tables: {
name: 'string',
type: 'string',
index_type: 'string',
key_names: 'string',
key_types: 'string',
},
ricardian_clauses: {
id: 'string',
body: 'string',
},
error_messages: {
error_code: 'number',
error_msg: 'string',
},
abi_extensions: {
tag: 'number',
value: 'string',
},
'variants?': {
name: 'string',
types: 'string',
},
'action_results?': {
name: 'string',
result_type: 'string',
},
'kv_tables?': {
todo: { // key is dynamic, using result from todo account
type: 'string',
primary_index: {
name: 'string',
type: 'string',
},
secondary_indices: {
'todo?': { // key is dynamic
type: 'string',
},
},
},
},
},
};
verifyType(result, getAbiResult);
});
it('validates return type of get_account', async () => {
const result: GetAccountResult = await rpc.get_account('zswchain');
const getAccountResult: any = {
account_name: 'string',
head_block_num: 'number',
head_block_time: 'string',
privileged: 'boolean',
last_code_update: 'string',
created: 'string',
'core_liquid_balance?': 'string',
ram_quota: 'number',
net_weight: 'number',
cpu_weight: 'number',
net_limit: {
used: 'number',
available: 'number',
max: 'number',
'last_usage_update_time?': 'string',
'current_used?': 'number',
},
cpu_limit: {
used: 'number',
available: 'number',
max: 'number',
'last_usage_update_time?': 'string',
'current_used?': 'number',
},
ram_usage: 'number',
permissions: {
perm_name: 'string',
parent: 'string',
required_auth: {
threshold: 'number',
keys: {
key: 'string',
weight: 'number',
},
accounts: {
permission: {
actor: 'string',
permission: 'string',
},
weight: 'number',
},
waits: {
wait_sec: 'number',
weight: 'number',
}
}
},
'total_resources&': {
owner: 'string',
ram_bytes: 'number',
net_weight: 'string',
cpu_weight: 'string',
},
'self_delegated_bandwidth&': {
from: 'string',
to: 'string',
net_weight: 'string',
cpu_weight: 'string',
},
'refund_request&': {
owner: 'string',
request_time: 'string',
net_amount: 'string',
cpu_amount: 'string',
},
'voter_info&': {
owner: 'string',
proxy: 'string',
producers: 'string',
staked: 'number',
last_vote_weight: 'string',
proxied_vote_weight: 'string',
is_proxy: 'number',
flags1: 'number',
reserved2: 'number',
reserved3: 'string',
},
'rex_info&': {
version: 'number',
owner: 'string',
vote_stake: 'string',
rex_balance: 'string',
matured_rex: 'number',
rex_maturities: 'any',
},
};
verifyType(result, getAccountResult);
});
it('validates return type of get_accounts_by_authorizers', async () => {
const result: GetAccountsByAuthorizersResult = await rpc.get_accounts_by_authorizers([
{ actor: 'bob', permission: 'active' },
{ actor: 'cfhello', permission: 'active' }
], ['ZSW7bxrQUTbQ4mqcoefhWPz1aFieN4fA9RQAiozRz7FrUChHZ7Rb8', 'ZSW6nVrBASwwviMy3CntKsb1cD5Ai2gRZnyrxJDqypL3JLL7KCKrK']);
const getAccountsByAuthorizersResult: any = {
accounts: {
account_name: 'string',
permission_name: 'string',
'authorizing_key?': 'string',
'authorizing_account?': {
actor: 'string',
permission: 'string',
},
weight: 'number',
threshold: 'number',
}
};
verifyType(result, getAccountsByAuthorizersResult);
});
it('validates return type of get_activated_protocol_features', async () => {
const result: GetActivatedProtocolFeaturesResult = await rpc.get_activated_protocol_features({});
const getActivatedProtocolFeaturesResult: any = {
activated_protocol_features: {
feature_digest: 'string',
activation_ordinal: 'number',
activation_block_num: 'number',
description_digest: 'string',
dependencies: 'string',
protocol_feature_type: 'string',
specification: {
name: 'string',
value: 'string',
},
},
'more?': 'number',
};
verifyType(result, getActivatedProtocolFeaturesResult);
});
it('validates return type of get_block_header_state', async () => {
const info: GetInfoResult = await rpc.get_info();
const result: GetBlockHeaderStateResult = await rpc.get_block_header_state(info.head_block_id);
const getBlockHeaderStateResult: any = {
id: 'string',
header: {
timestamp: 'string',
producer: 'string',
confirmed: 'number',
previous: 'string',
transaction_mroot: 'string',
action_mroot: 'string',
schedule_version: 'number',
'new_producers?': {
version: 'number',
producers: {
producer_name: 'string',
block_signing_key: 'string',
},
},
header_extensions: 'any',
producer_signature: 'string',
},
pending_schedule: {
schedule_lib_num: 'number',
schedule_hash: 'string',
schedule: {
version: 'number',
producers: {
producer_name: 'string',
block_signing_key: 'string',
},
},
},
activated_protocol_features: {
protocol_features: 'string',
},
additional_signatures: 'string',
block_num: 'number',
dpos_proposed_irreversible_blocknum: 'number',
dpos_irreversible_blocknum: 'number',
active_schedule: {
version: 'number',
producers: {
producer_name: 'string',
authority: [ 'number|string', {
threshold: 'number',
keys: {
key: 'string',
weight: 'number',
},
}],
},
},
blockroot_merkle: {
_active_nodes: 'string',
_node_count: 'number',
},
producer_to_last_produced: 'Map<string, number>',
producer_to_last_implied_irb: 'Map<string, number>',
valid_block_signing_authority: [ 'number|string', {
threshold: 'number',
keys: {
key: 'string',
weight: 'number',
},
}],
confirm_count: 'number',
state_extension: [ 'number', {
security_group_info: {
version: 'number',
participants: 'string',
},
}]
};
verifyType(result, getBlockHeaderStateResult);
});
it('validates return type of get_block_info', async () => {
const info: GetInfoResult = await rpc.get_info();
const result: GetBlockInfoResult = await rpc.get_block_info(info.last_irreversible_block_num);
const getBlockInfoResult: any = {
timestamp: 'string',
producer: 'string',
confirmed: 'number',
previous: 'string',
transaction_mroot: 'string',
action_mroot: 'string',
schedule_version: 'number',
producer_signature: 'string',
id: 'string',
block_num: 'number',
ref_block_num: 'number',
ref_block_prefix: 'number',
};
verifyType(result, getBlockInfoResult);
});
it('validates return type of get_block', async () => {
const info: GetInfoResult = await rpc.get_info();
const result: GetBlockResult = await rpc.get_block(info.last_irreversible_block_num);
const getBlockResult: any = {
timestamp: 'string',
producer: 'string',
confirmed: 'number',
previous: 'string',
transaction_mroot: 'string',
action_mroot: 'string',
schedule_version: 'number',
'new_producers&': {
version: 'number',
producers: {
producer_name: 'string',
block_signing_key: 'string'
}
},
producer_signature: 'string',
transactions: {
status: 'string',
cpu_usage_us: 'number',
net_usage_words: 'number',
trx: {
id: 'string',
signatures: 'string',
compression: 'number|string',
packed_context_free_data: 'string',
context_free_data: 'string',
packed_trx: 'string',
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?': {
account: 'string',
name: 'string',
authorization: {
actor: 'string',
permission: 'string',
},
'data?': 'any',
'hex_data?': 'string',
},
'context_free_data?': 'number',
actions: {
account: 'string',
name: 'string',
authorization: {
actor: 'string',
permission: 'string',
},
'data?': 'any',
'hex_data?': 'string',
},
'transaction_extensions?': '[number, string]',
},
},
},
id: 'string',
block_num: 'number',
ref_block_prefix: 'number',
};
verifyType(result, getBlockResult);
});
it('validates return type of get_code', async () => {
const result: GetCodeResult = await rpc.get_code('todo');
const getCodeResult: any = {
account_name: 'string',
code_hash: 'string',
wast: 'string',
wasm: 'string',
'abi?': {
version: 'string',
types: {
new_type_name: 'string',
type: 'string',
},
structs: {
name: 'string',
base: 'string',
fields: {
name: 'string',
type: 'string',
},
},
actions: {
name: 'string',
type: 'string',
ricardian_contract: 'string',
},
tables: {
name: 'string',
type: 'string',
index_type: 'string',
key_names: 'string',
key_types: 'string',
},
ricardian_clauses: {
id: 'string',
body: 'string',
},
error_messages: {
error_code: 'number',
error_msg: 'string',
},
abi_extensions: {
tag: 'number',
value: 'string',
},
'variants?': {
name: 'string',
types: 'string',
},
'action_results?': {
name: 'string',
result_type: 'string',
},
'kv_tables?': {
todo: { // key is dynamic, using result from todo account
type: 'string',
primary_index: {
name: 'string',
type: 'string',
},
secondary_indices: {
'todo?': { // key is dynamic
type: 'string',
},
},
},
},
},
};
verifyType(result, getCodeResult);
});
it('validates return type of get_code_hash', async () => {
const result: GetCodeHashResult = await rpc.get_code_hash('todo');
const getCodeHashResult: any = {
account_name: 'string',
code_hash: 'string',
};
verifyType(result, getCodeHashResult);
});
it('validates return type of get_currency_balance', async () => {
const result: string[] = await rpc.get_currency_balance('zsw.token', 'bob', 'SYS');
result.forEach((element: any) => {
expect(typeof element).toEqual('string');
});
});
it('validates return type of get_currency_stats', async () => {
const result: GetCurrencyStatsResult = await rpc.get_currency_stats('zsw.token', 'SYS');
const getCurrencyStatsResult: any = {
SYS: {
supply: 'string',
max_supply: 'string',
issuer: 'string',
}
};
verifyType(result, getCurrencyStatsResult);
});
it('validates return type of get_info', async () => {
const result: GetInfoResult = await rpc.get_info();
const getInfoResult: any = {
server_version: 'string',
chain_id: 'string',
head_block_num: 'number',
last_irreversible_block_num: 'number',
last_irreversible_block_id: 'string',
'last_irreversible_block_time?': 'string',
head_block_id: 'string',
head_block_time: 'string',
head_block_producer: 'string',
virtual_block_cpu_limit: 'number',
virtual_block_net_limit: 'number',
block_cpu_limit: 'number',
block_net_limit: 'number',
'server_version_string?': 'string',
'fork_db_head_block_num?': 'number',
'fork_db_head_block_id?': 'string',
'server_full_version_string?': 'string',
'first_block_num?': 'number',
};
verifyType(result, getInfoResult);
});
it('validates return type of get_producer_schedule', async () => {
const result: GetProducerScheduleResult = await rpc.get_producer_schedule();
const getProducerScheduleResult: any = {
'active&': {
version: 'number',
producers: {
producer_name: 'string',
authority: [ 'number|string', {
threshold: 'number',
keys: {
key: 'string',
weight: 'number',
},
}],
},
},
'pending&': {
version: 'number',
producers: {
producer_name: 'string',
authority: [ 'number|string', {
threshold: 'number',
keys: {
key: 'string',
weight: 'number',
},
}],
},
},
'proposed&': {
version: 'number',
producers: {
producer_name: 'string',
authority: [ 'number|string', {
threshold: 'number',
keys: {
key: 'string',
weight: 'number',
},
}],
},
},
};
verifyType(result, getProducerScheduleResult);
});
it('validates return type of get_producers', async () => {
const result: GetProducersResult = await rpc.get_producers();
const getProducersResult: any = {
rows: {
owner: 'string',
'producer_authority?': [ 'number|string', {
threshold: 'number',
keys: {
key: 'string',
weight: 'number',
},
}],
url: 'string',
'is_active?': 'number',
total_votes: 'string',
producer_key: 'string',
'unpaid_blocks?': 'number',
'last_claim_time?': 'string',
'location?': 'number',
},
total_producer_vote_weight: 'string',
more: 'string',
};
verifyType(result, getProducersResult);
});
it('validates return type of get_raw_code_and_abi', async () => {
const result: GetRawCodeAndAbiResult = await rpc.get_raw_code_and_abi('zswchain');
const getRawCodeAndAbiResult: any = {
account_name: 'string',
wasm: 'string',
abi: 'string',
};
verifyType(result, getRawCodeAndAbiResult);
});
it('validates return type of get_raw_abi', async () => {
const result: GetRawAbiResult = await rpc.get_raw_abi('zswchain');
const getRawAbiResult: any = {
account_name: 'string',
code_hash: 'string',
abi_hash: 'string',
abi: 'string',
};
verifyType(result, getRawAbiResult);
});
it('validates return type of get_scheduled_transactions', async () => {
const result: GetScheduledTransactionsResult = await rpc.get_scheduled_transactions();
const getScheduledTransactionsResult: any = {
transactions: {
trx_id: 'string',
sender: 'string',
sender_id: 'string',
payer: 'string',
delay_until: 'string',
expiration: 'string',
published: 'string',
'packed_trx?': 'string',
'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?': {
account: 'string',
name: 'string',
authorization: {
actor: 'string',
permission: 'string',
},
'data?': 'any',
'hex_data?': 'string',
},
'context_free_data?': 'number',
'actions': {
account: 'string',
name: 'string',
authorization: {
actor: 'string',
permission: 'string',
},
'data?': 'any',
'hex_data?': 'string',
},
'transaction_extensions?': '[number, string]',
'deferred_transaction_generation?': {
sender_trx_id: 'string',
sender_id: 'string',
sender: 'string',
},
},
},
more: 'string',
};
verifyType(result, getScheduledTransactionsResult);
});
it('validates return type of get_table_rows', async () => {
const result: GetTableRowsResult = await rpc.get_table_rows({
code: 'zsw.token',
scope: 'zsw.token',
table: 'accounts',
});
const getTableRowsResult: any = {
rows: 'any',
more: 'boolean',
next_key: 'string',
next_key_bytes: 'string',
};
verifyType(result, getTableRowsResult);
});
it('validates return type of get_kv_table_rows', async () => {
const result: GetTableRowsResult = await rpc.get_kv_table_rows({
code: 'todo',
table: 'todo',
index_name: 'map.index',
encode_type: 'string',
});
const getTableRowsResult: any = {
rows: 'any',
more: 'boolean',
next_key: 'string',
next_key_bytes: 'string',
};
verifyType(result, getTableRowsResult);
});
it('validates return type of get_table_by_scope', async () => {
const result: GetTableByScopeResult = await rpc.get_table_by_scope({
code: 'zsw.token',
table: 'accounts',
});
const getTableByScopeResult: any = {
rows: 'any',
more: 'string',
};
verifyType(result, getTableByScopeResult);
});
it('validates return type of get_required_keys', async () => {
const info = await rpc.get_info();
let transaction: Transaction = {
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'bob',
permission: 'active',
}],
data: {
from: 'bob',
to: 'alice',
quantity: '0.0001 SYS',
memo: '',
},
}],
context_free_actions: []
};
transaction = {
...ser.transactionHeader({
block_num: info.last_irreversible_block_num,
id: info.last_irreversible_block_id,
timestamp: info.last_irreversible_block_time,
}, 30),
context_free_actions: await api.serializeActions(transaction.context_free_actions || []),
actions: await api.serializeActions(transaction.actions),
...transaction,
};
const availableKeys = await signatureProvider.getAvailableKeys();
const result: string[] = await rpc.getRequiredKeys({ transaction, availableKeys });
result.forEach((element: any) => {
expect(typeof element).toEqual('string');
});
});
it('validates return type of push_transaction', async () => {
const transaction: PushTransactionArgs = await api.transact({
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'bob',
permission: 'active',
}],
data: {
from: 'bob',
to: 'alice',
quantity: '0.0001 SYS',
memo: '',
},
}],
}, {
sign: true,
broadcast: false,
useLastIrreversible: true,
expireSeconds: 30,
}) as PushTransactionArgs;
const result: TransactResult = await rpc.push_transaction(transaction);
const transactResult = {
transaction_id: 'string',
processed: {
id: 'string',
block_num: 'number',
block_time: 'string',
'producer_block_id&': 'string',
'receipt&': {
status: 'string',
cpu_usage_us: 'number',
net_usage_words: 'number',
},
elapsed: 'number',
net_usage: 'number',
scheduled: 'boolean',
action_traces: {
action_ordinal: 'number',
creator_action_ordinal: 'number',
closest_unnotified_ancestor_action_ordinal: 'number',
receipt: {
receiver: 'string',
act_digest: 'string',
global_sequence: 'number',
recv_sequence: 'number',
auth_sequence: [ 'string', 'number' ],
code_sequence: 'number',
abi_sequence: 'number',
},
receiver: 'string',
act: {
account: 'string',
name: 'string',
authorization: {
actor: 'string',
permission: 'string',
},
'data?': 'any',
'hex_data?': 'string',
},
context_free: 'boolean',
elapsed: 'number',
console: 'string',
trx_id: 'string',
block_num: 'number',
block_time: 'string',
'producer_block_id&': 'string',
account_ram_deltas: {
account: 'string',
delta: 'number',
},
account_disk_deltas: {
account: 'string',
delta: 'number',
},
except: 'any',
'error_code&': 'number',
'return_value?': 'any',
'return_value_hex_data?': 'string',
'return_value_data?': 'any',
'inline_traces?': 'any', // ActionTrace, recursive?
},
'account_ram_delta&': {
account: 'string',
delta: 'number',
},
'except&': 'string',
'error_code&': 'number',
bill_to_accounts: 'string',
},
};
verifyType(result, transactResult);
});
it('validates return type of push_ro_transaction', async () => {
const transaction: PushTransactionArgs = await api.transact({
actions: [{
account: 'readonly',
name: 'get',
authorization: [{
actor: 'readonly',
permission: 'active',
}],
data: {},
}],
}, {
sign: true,
broadcast: false,
useLastIrreversible: true,
expireSeconds: 30,
}) as PushTransactionArgs;
const result: ReadOnlyTransactResult = await rpc.push_ro_transaction(transaction);
const readOnlyTransactResult: any = {
head_block_num: 'number',
head_block_id: 'string',
last_irreversible_block_num: 'number',
last_irreversible_block_id: 'string',
code_hash: 'string',
pending_transactions: 'string',
result: {
id: 'string',
block_num: 'number',
block_time: 'string',
'producer_block_id&': 'string',
'receipt&': {
status: 'string',
cpu_usage_us: 'number',
net_usage_words: 'number',
},
elapsed: 'number',
net_usage: 'number',
scheduled: 'boolean',
action_traces: {
action_ordinal: 'number',
creator_action_ordinal: 'number',
closest_unnotified_ancestor_action_ordinal: 'number',
receipt: {
receiver: 'string',
act_digest: 'string',
global_sequence: 'number',
recv_sequence: 'number',
auth_sequence: [ 'string', 'number' ],
code_sequence: 'number',
abi_sequence: 'number',
},
receiver: 'string',
act: {
account: 'string',
name: 'string',
authorization: {
actor: 'string',
permission: 'string',
},
'data?': 'any',
'hex_data?': 'string',
},
context_free: 'boolean',
elapsed: 'number',
console: 'string',
trx_id: 'string',
block_num: 'number',
block_time: 'string',
'producer_block_id&': 'string',
account_ram_deltas: {
account: 'string',
delta: 'number',
},
account_disk_deltas: {
account: 'string',
delta: 'number',
},
except: 'any',
'error_code&': 'number',
'return_value?': 'any',
'return_value_hex_data?': 'string',
'return_value_data?': 'any',
'inline_traces?': 'any', // ActionTrace, recursive?
},
'account_ram_delta&': {
account: 'string',
delta: 'number',
},
'except&': 'string',
'error_code&': 'number',
bill_to_accounts: 'string',
}
};
verifyType(result, readOnlyTransactResult);
});
it('validates return type of push_transactions', async () => {
const transactionA: PushTransactionArgs = await api.transact({
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'bob',
permission: 'active',
}],
data: {
from: 'bob',
to: 'alice',
quantity: '0.0001 SYS',
memo: 'A',
},
}],
}, {
sign: true,
broadcast: false,
useLastIrreversible: true,
expireSeconds: 30,
}) as PushTransactionArgs;
const transactionB: PushTransactionArgs = await api.transact({
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'bob',
permission: 'active',
}],
data: {
from: 'bob',
to: 'alice',
quantity: '0.0001 SYS',
memo: 'B',
},
}],
}, {
sign: true,
broadcast: false,
useLastIrreversible: true,
expireSeconds: 30,
}) as PushTransactionArgs;
const result: TransactResult[] = await rpc.push_transactions([ transactionA, transactionB ]);
const transactResult = {
transaction_id: 'string',
processed: {
id: 'string',
block_num: 'number',
block_time: 'string',
'producer_block_id&': 'string',
'receipt&': {
status: 'string',
cpu_usage_us: 'number',
net_usage_words: 'number',
},
elapsed: 'number',
net_usage: 'number',
scheduled: 'boolean',
action_traces: {
action_ordinal: 'number',
creator_action_ordinal: 'number',
closest_unnotified_ancestor_action_ordinal: 'number',
receipt: {
receiver: 'string',
act_digest: 'string',
global_sequence: 'number',
recv_sequence: 'number',
auth_sequence: [ 'string', 'number' ],
code_sequence: 'number',
abi_sequence: 'number',
},
receiver: 'string',
act: {
account: 'string',
name: 'string',
authorization: {
actor: 'string',
permission: 'string',
},
'data?': 'any',
'hex_data?': 'string',
},
context_free: 'boolean',
elapsed: 'number',
console: 'string',
trx_id: 'string',
block_num: 'number',
block_time: 'string',
'producer_block_id&': 'string',
account_ram_deltas: {
account: 'string',
delta: 'number',
},
account_disk_deltas: {
account: 'string',
delta: 'number',
},
except: 'any',
'error_code&': 'number',
'return_value?': 'any',
'return_value_hex_data?': 'string',
'return_value_data?': 'any',
'inline_traces?': 'any', // ActionTrace, recursive?
},
'account_ram_delta&': {
account: 'string',
delta: 'number',
},
'except&': 'string',
'error_code&': 'number',
bill_to_accounts: 'string',
},
};
result.forEach((transaction: TransactResult) => {
verifyType(transaction, transactResult);
});
});
it('validates return type of send_transaction', async () => {
const transaction: PushTransactionArgs = await api.transact({
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'alice',
permission: 'active',
}],
data: {
from: 'alice',
to: 'bob',
quantity: '0.0001 SYS',
memo: '',
},
}],
}, {
sign: true,
broadcast: false,
useLastIrreversible: true,
expireSeconds: 30,
}) as PushTransactionArgs;
const result: TransactResult = await rpc.send_transaction(transaction);
const transactResult = {
transaction_id: 'string',
processed: {
id: 'string',
block_num: 'number',
block_time: 'string',
'producer_block_id&': 'string',
'receipt&': {
status: 'string',
cpu_usage_us: 'number',
net_usage_words: 'number',
},
elapsed: 'number',
net_usage: 'number',
scheduled: 'boolean',
action_traces: {
action_ordinal: 'number',
creator_action_ordinal: 'number',
closest_unnotified_ancestor_action_ordinal: 'number',
receipt: {
receiver: 'string',
act_digest: 'string',
global_sequence: 'number',
recv_sequence: 'number',
auth_sequence: [ 'string', 'number' ],
code_sequence: 'number',
abi_sequence: 'number',
},
receiver: 'string',
act: {
account: 'string',
name: 'string',
authorization: {
actor: 'string',
permission: 'string',
},
'data?': 'any',
'hex_data?': 'string',
},
context_free: 'boolean',
elapsed: 'number',
console: 'string',
trx_id: 'string',
block_num: 'number',
block_time: 'string',
'producer_block_id&': 'string',
account_ram_deltas: {
account: 'string',
delta: 'number',
},
account_disk_deltas: {
account: 'string',
delta: 'number',
},
except: 'any',
'error_code&': 'number',
'return_value?': 'any',
'return_value_hex_data?': 'string',
'return_value_data?': 'any',
'inline_traces?': 'any', // ActionTrace, recursive?
},
'account_ram_delta&': {
account: 'string',
delta: 'number',
},
'except&': 'string',
'error_code&': 'number',
bill_to_accounts: 'string',
},
};
verifyType(result, transactResult);
});
});
describe('DB Size API Plugin Endpoints', () => {
it('validates return type of get', async () => {
const result: DBSizeGetResult = await rpc.db_size_get();
const dbSizeGetResult: any = {
free_bytes: 'number',
used_bytes: 'number',
size: 'number',
indices: {
index: 'string',
row_count: 'number',
},
};
verifyType(result, dbSizeGetResult);
});
});
describe('Trace API Plugin Endpoints', () => {
it('validates return type of get_block', async () => {
const info: GetInfoResult = await rpc.get_info();
const result: TraceApiGetBlockResult = await rpc.trace_get_block(info.last_irreversible_block_num);
const traceApiGetBlockResult: any = {
id: 'string',
number: 'number',
previous_id: 'string',
status: 'string',
timestamp: 'string',
producer: 'string',
transaction_mroot: 'string',
action_mroot: 'string',
schedule_version: 'number',
transactions: {
id: 'string',
actions: {
global_sequence: 'number',
receiver: 'string',
account: 'string',
action: 'string',
authorization: {
account: 'string',
permission: 'string'
},
data: 'string',
return_value: 'string',
},
status: 'string',
cpu_usage_us: 'number',
net_usage_words: 'number',
signatures: 'string',
transaction_header: 'any',
bill_to_accounts: 'string',
},
};
verifyType(result, traceApiGetBlockResult);
});
*/
});
const verifyType = (data: any, type: any): void => {
const verifiedKeys: string[] = Object.keys(type).filter((key: string) => {
const formattedKey = key.replace('?', '').replace('&', '');
if (key.includes('?')) {
if (!data.hasOwnProperty(formattedKey)) return false;
}
return true;
}).map((key: string) => {
const formattedKey = key.replace('?', '').replace('&', '');
if (Array.isArray(data[formattedKey])) {
if (Array.isArray(type[key])) {
data[formattedKey].forEach((element: any, index: number) => {
if (Array.isArray(element)) {
element.forEach((secondElement: any, secondIndex: number) => {
complexOrPrimitive(secondElement, type[key][secondIndex], formattedKey);
});
} else {
complexOrPrimitive(element, type[key][index], formattedKey);
}
});
} else {
data[formattedKey].forEach((element: any) => {
complexOrPrimitive(element, type[key], formattedKey);
});
}
} else if (key.includes('&')) {
if (data[formattedKey] !== null) {
complexOrPrimitive(data[formattedKey], type[key], formattedKey);
}
} else {
complexOrPrimitive(data[formattedKey], type[key], formattedKey);
}
return formattedKey;
});
expect(data).toContainAllKeys(verifiedKeys);
};
const complexOrPrimitive = (data: any, type: any, formattedKey: any): void => {
if (typeof type === 'object') {
verifyType(data, type);
} else if (type.includes('Map')) {
const types = type.replace('Map<', '').replace('>', '').split(', ');
data.forEach((value: any, index: number) => {
complexOrPrimitive(value, types[index], formattedKey);
});
} else if (type.includes('|')) {
const types = type.split('|');
expect(typeof data).toBeOneOf(types);
} else if (type !== 'any') {
expect(typeof data).toEqual(type);
}
};
* {
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;
}
}
<!DOCTYPE html>
<html>
<head>
<link rel='stylesheet' type='text/css' href='web.css'>
<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>
<script>
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 zswjs_jsonrpc.JsonRpc('http://localhost:8888');
const signatureProvider = new zswjs_jssig.JsSignatureProvider([privateKey, r1PrivateKey, cfactorPrivateKey]);
const api = new zswjs_api.Api({ rpc, signatureProvider });
const EXECUTING = 'Executing Test', SUCCESS = 'Success', FAILED = 'Failed';
let resultsLabel, transactionResponse, transactionSignatures, failedAsPlanned;
const transactWithConfig = async (config, memo, from = 'bob', to = 'alice') => {
return api.transact({
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: from,
permission: 'active',
}],
data: {
from,
to,
quantity: '0.0001 SYS',
memo,
},
}]
}, config);
}
const testTransactWithConfigBlocksBehind = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse = await transactWithConfig({ blocksBehind: 3, expireSeconds: 30 }, 'transactWithBlocksBehind');
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact With Config Blocks Behind Test Failure: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
const testTransactWithConfigUseLastIrreversible = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse = await transactWithConfig({ useLastIrreversible: true, expireSeconds: 30 }, 'transactWithUseLastIrreversible');
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact With Config Use Last Irreversible Test Failure: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
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 testTransactWithoutConfig = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse = await transactWithoutConfig();
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact without Config Test Failure: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
};
const testTransactWithCompression = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse =
await transactWithConfig({ blocksBehind: 3, expireSeconds: 30, compression: true }, 'transactWithCompression');
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact With Config Test Failure: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
const transactWithContextFree = async (user, context_free_data) => {
return api.transact({
actions: [{
account: 'cfhello',
name: 'normal',
authorization: [{
actor: 'cfactor',
permission: 'active'
}],
data: {
user
}
}],
context_free_actions: [{
account: 'cfhello',
name: 'contextfree',
authorization: [],
data: {}
}],
context_free_data
}, {
blocksBehind: 3,
expireSeconds: 30
});
}
const testTransactWithContextFreeAction = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse =
await transactWithContextFree('test', []);
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact With Context Free Action Test Failure: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
const testTransactWithContextFreeData = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse =
await transactWithContextFree('test2', [[ '74657374', '7465737464617461' ]]);
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact With Context Free Data Test Failure: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
const testTransactWithoutBroadcast = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionSignatures =
await transactWithConfig({ broadcast: false, blocksBehind: 3, expireSeconds: 30 }, 'transactWithoutBroadcast');
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact without Broadcast Test Failure: ', error.message);
return false;
}
if(transactionSignatures.signatures && transactionSignatures.serializedTransaction) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
};
const broadcastResult = async (signaturesAndPackedTransaction) => await api.pushSignedTransaction(signaturesAndPackedTransaction);
const testBroadcastResult = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionSignatures =
await transactWithConfig({ broadcast: false, blocksBehind: 3, expireSeconds: 30 }, 'transactWithoutBroadcast2');
transactionResponse = await broadcastResult(transactionSignatures);
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Broadcast Transaction Test Failure: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
const shorthandWithApiJson = async () => {
await api.getAbi('zsw.token');
return api.transact({
actions: [
api.with('zsw.token').as('bob').transfer('bob', 'alice', '0.0001 SYS', 'transactWithShorthandApiJson')
]
}, {
blocksBehind: 3,
expireSeconds: 30
});
}
const testShorthandWithApiJson = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse = await shorthandWithApiJson();
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact with .with() Using Api and Json Abi Test Failure: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
};
const shorthandWithTxJson = async () => {
await api.getAbi('zsw.token');
const tx = api.buildTransaction();
tx.with('zsw.token').as('bob').transfer('bob', 'alice', '0.0001 SYS', 'transactWithShorthandTxJson');
return tx.send({
blocksBehind: 3,
expireSeconds: 30
});
}
const testShorthandWithTxJson = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse = await shorthandWithTxJson();
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact with .with() Using Tx and Json Abi Test Failure: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
};
const shorthandWithTxJsonContextFree = async (user, context_free_data) => {
await api.getAbi('cfhello');
const tx = api.buildTransaction();
tx.associateContextFree(() => ({
contextFreeAction: tx.with('cfhello').as().contextfree(),
action: tx.with('cfhello').as('cfactor').normal(user),
context_free_data
}));
return await tx.send({
blocksBehind: 3,
expireSeconds: 30
});
}
const testShorthandWithTxJsonContextFreeAction = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse = await shorthandWithTxJsonContextFree('test');
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact with .with() Using Tx and Json Abi with Context Free Action: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
const testShorthandWithTxJsonContextFreeData = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse = await shorthandWithTxJsonContextFree('test2', [ '74657374', '7465737464617461' ]);
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact with .with() Using Tx and Json Abi with Context Free Data: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
const testWithP256EllipticCurve = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse = await transactWithConfig({ blocksBehind: 3, expireSeconds: 30 }, 'transactWithR1KeySignature', 'bobr1', 'alicer1');
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact elliptic p256/KeyType.r1 Keys and Signatures Test Failure: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
};
const returnValueTx = async () => {
await api.getAbi('returnvalue');
const tx = api.buildTransaction();
tx.with('returnvalue').as('bob').sum(5, 5);
return tx.send({
blocksBehind: 3,
expireSeconds: 30
});
}
const testWithReturnValueTx = async (e) => {
const expectedValue = 10;
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse = await returnValueTx();
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact Return Values Test Failure: ', error.message);
return false;
}
if (transactionResponse.processed.action_traces[0].return_value_data === expectedValue) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
const resourcePayerTx = 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 testWithResourcePayerTx = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse = await resourcePayerTx();
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact Resource Payer Test Failure: ', error.message);
return false;
}
if (transactionResponse.transaction_id) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
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 testWithReadOnlyQuery = async (e) => {
const expectedValue = [
{'id': 1, 'name': 'Bob Smith', 'gender': 1, 'age': 25},
{'id': 3, 'name': 'John Smith', 'gender': 1, 'age': 42},
{'id': 4, 'name': 'Jack Smith', 'gender': 1, 'age': 27},
{'id': 2, 'name': 'Alice Smith', 'gender': 0, 'age': 20,},
{'id': 5, 'name': 'Youko Niihara', 'gender': 0, 'age': 26},
{'id': 6, 'name': 'Rose Lee', 'gender': 0, 'age': 18},
{'id': 7, 'name': 'Youko Kawakami', 'gender': 0, 'age': 25},
{'id': 8, 'name': 'Yuu Yamada', 'gender': 0, 'age': 24}
];
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
transactionResponse = await readOnlyQuery();
} catch (error) {
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
console.error('Transact Read Only Query Test Failure: ', error.message);
return false;
}
if (JSON.stringify(transactionResponse.result.action_traces[0].return_value_data) === JSON.stringify(expectedValue)) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
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 testWithReadOnlyFailureTrace = async (e) => {
let err;
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
await readOnlyFailureTrace();
} catch (err) {
if (err.details.code === 3090004 && err.details.stack[0].format === 'missing authority of ${account}') {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
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 testTransactShouldFail = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
await transactShouldFail();
} catch (e) {
if (e.message === 'Required configuration or TAPOS fields are not present') {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
const rpcShouldFail = async () => await rpc.get_block_info(-1);
const testRpcShouldFail = async (e) => {
resultsLabel = e.target;
resultsLabel.innerText = EXECUTING;
try {
await rpcShouldFail();
} catch (e) {
if (e instanceof zswjs_jsonrpc.RpcError) {
resultsLabel.className = "success";
resultsLabel.innerText = SUCCESS;
return true;
}
}
resultsLabel.className = 'failed';
resultsLabel.innerText = FAILED;
return false;
}
const runAllTests = async () => {
const buttons = document.getElementsByTagName('button');
for (var i = 1; i < buttons.length; i++) {
var button = buttons[i];
button.click();
await new Promise(resolve => setTimeout(resolve, 150))
}
return;
}
</script>
</head>
<body>
<div class='header-container'>
<div class='header'>
<img src='logo.svg'/>
<h1>Web Build Integration Tests</h1>
<button onClick='runAllTests();'>Run All Tests</button>
</div>
</div>
<div class='tests'>
<div><h2>Transact with blocksBehind Configuration Parameter</h2><button id='testTransactWithConfigBlocksBehind' onClick='testTransactWithConfigBlocksBehind(event);'>Test</button></div>
<div><h2>Transact with useLastIrreversible Configuration Parameter</h2><button id='testTransactWithConfigUseLastIrreversible' onClick='testTransactWithConfigUseLastIrreversible(event);'>Test</button></div>
<div><h2>Transact with Manually Configured TAPOS</h2><button id='testTransactWithoutConfig' onClick='testTransactWithoutConfig(event);'>Test</button></div>
<div><h2>Transact with Compression</h2><button id='testTransactWithCompression' onClick='testTransactWithCompression(event);'>Test</button></div>
<div><h2>Transact with Context Free Action</h2><button id='testTransactWithContextFreeAction' onClick='testTransactWithContextFreeAction(event);'>Test</button></div>
<div><h2>Transact with Context Free Data</h2><button id='testTransactWithContextFreeData' onClick='testTransactWithContextFreeData(event);'>Test</button></div>
<div><h2>Transact without Broadcasting</h2><button id='testTransactWithoutBroadcast' onClick='testTransactWithoutBroadcast(event);'>Test</button></div>
<div><h2>Broadcast Transaction</h2><button id='testBroadcastResult' onClick='testBroadcastResult(event);'>Test</button></div>
<div><h2>Transact with .with() Using Api and Json Abi</h2><button id='testShorthandWithApiJson' onClick='testShorthandWithApiJson(event);'>Test</button></div>
<div><h2>Transact with .with() Using Tx and Json Abi</h2><button id='testShorthandWithTxJson' onClick='testShorthandWithTxJson(event);'>Test</button></div>
<div><h2>Transact with .with() Using Tx and Json Abi with Context Free Action</h2><button id='testShorthandWithTxJsonContextFreeAction' onClick='testShorthandWithTxJsonContextFreeAction(event);'>Test</button></div>
<div><h2>Transact with .with() Using Tx and Json Abi with Context Free Data</h2><button id='testShorthandWithTxJsonContextFreeData' onClick='testShorthandWithTxJsonContextFreeData(event);'>Test</button></div>
<div><h2>Transact elliptic p256/KeyType.r1 Keys and Signatures</h2><button id='testWithP256EllipticCurve' onClick='testWithP256EllipticCurve(event);'>Test</button></div>
<div><h2>Transact Return Values</h2><button id='testWithReturnValueTx' onClick='testWithReturnValueTx(event);'>Test</button></div>
<div><h2>Resource Payer</h2><button id='testWithResourcePayerTx' onClick='testWithResourcePayerTx(event);'>Test</button></div>
<div><h2>Read Only Query</h2><button id='testWithReadOnlyQuery' onClick='testWithReadOnlyQuery(event);'>Test</button></div>
<div><h2>Read Only Failure Trace</h2><button id='testWithReadOnlyFailureTrace' onClick='testWithReadOnlyFailureTrace(event);'>Test</button></div>
<div><h2>Invalid Transaction Throws Error</h2><button id='testTransactShouldFail' onClick='testTransactShouldFail(event);'>Test</button></div>
<div><h2>Invalid Rpc Call Throws Rpc Error</h2><button id='testRpcShouldFail' onClick='testRpcShouldFail(event);'>Test</button></div>
</div>
</body>
</html>
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 { Api } from '../zswjs-api';
import { JsonRpc } from '../zswjs-jsonrpc';
import { JsSignatureProvider } from '../zswjs-jssig';
import * as path from 'path';
import * as fs from 'fs';
import { Action } from '../zswjs-serialize';
import { base64ToBinary } from '../zswjs-numeric';
const transaction = {
expiration: '2018-09-04T18:42:49',
ref_block_num: 38096,
ref_block_prefix: 505360011,
max_net_usage_words: 0,
max_cpu_usage_ms: 0,
delay_sec: 0,
context_free_actions: [] as Action[],
actions: [
{
account: 'tstzswcredit',
name: 'transfer',
authorization: [
{
actor: 'thegazelle',
permission: 'active',
},
],
data: {
from: 'thegazelle',
to: 'remasteryoda',
quantity: '1.0000 ZSWCC',
memo: 'For a secure future.',
},
hex_data: `00808A517DC354CB6012F557656CA4BA1027000000000000045A53574343000014466F722
06120736563757265206675747572652E`,
},
{
account: 'tstzswcredit',
name: 'transfer',
authorization: [
{
actor: 'thegazelle',
permission: 'active',
},
],
data: {
from: 'thegazelle',
to: 'remasteryoda',
quantity: '2.0000 ZSWCC',
memo: 'For a second secure future (multiverse?)',
},
hex_data: `00808A517DC354CB6012F557656CA4BA204E000000000000045A53574343000028466F722061207365636F6E642073656
37572652066757475726520286D756C746976657273653F29`,
},
],
transaction_extensions: [] as any,
};
const serializedTx = [
41, 210, 142, 91, 208, 148, 139, 46, 31, 30, 0, 0, 0, 0, 2, 144, 93, 82,
23, 113, 252, 51, 206, 0, 0, 0, 87, 45, 60, 205, 205, 1, 0, 128, 138, 81,
125, 195, 84, 203, 0, 0, 0, 0, 168, 237, 50, 50, 0, 144, 93, 82, 23, 113,
252, 51, 206, 0, 0, 0, 87, 45, 60, 205, 205, 1, 0, 128, 138, 81, 125, 195,
84, 203, 0, 0, 0, 0, 168, 237, 50, 50, 0, 0
];
const deserializedTx = {
actions: [
{
account: 'tstzswcredit',
authorization: [{ actor: 'thegazelle', permission: 'active' }],
data: '',
name: 'transfer',
},
{
account: 'tstzswcredit',
authorization: [{ actor: 'thegazelle', permission: 'active' }],
data: '',
name: 'transfer',
},
],
context_free_actions: [] as any,
delay_sec: 0,
expiration: '2018-09-04T18:42:49.000',
max_cpu_usage_ms: 0,
max_net_usage_words: 0,
ref_block_num: 38096,
ref_block_prefix: 505360011,
transaction_extensions: [] as any,
};
const serializedActions = [
{
account: 'tstzswcredit',
authorization: [{ actor: 'thegazelle', permission: 'active' }],
data: '00808A517DC354CB6012F557656CA4BA1027000000000000045A53574343000014466F72206120736563757265206675747572652E', // eslint-disable-line
name: 'transfer',
},
{
account: 'tstzswcredit',
authorization: [{ actor: 'thegazelle', permission: 'active' }],
data: '00808A517DC354CB6012F557656CA4BA204E000000000000045A53574343000028466F722061207365636F6E64207365637572652066757475726520286D756C746976657273653F29', // eslint-disable-line
name: 'transfer',
},
];
const deserializedActions = [
{
account: 'tstzswcredit',
authorization: [{ actor: 'thegazelle', permission: 'active' }],
data: {
from: 'thegazelle',
memo: 'For a secure future.',
quantity: '1.0000 ZSWCC',
to: 'remasteryoda',
},
name: 'transfer',
},
{
account: 'tstzswcredit',
authorization: [{ actor: 'thegazelle', permission: 'active' }],
data: {
from: 'thegazelle',
memo: 'For a second secure future (multiverse?)',
quantity: '2.0000 ZSWCC',
to: 'remasteryoda',
},
name: 'transfer',
},
];
describe('zswjs-api', () => {
let api: any;
let rpc: any;
const fetch = async (input: any, init: any): Promise<any> => ({
ok: true,
json: async () => {
if (input === '/v1/chain/get_raw_abi') {
return {
account_name: 'tstzswcredit',
abi: 'DmVvc2lvOjphYmkvMS4wAQxhY2NvdW50X25hbWUEbmFtZQUIdHJhbnNmZXIABARmcm9tDGFjY291bnRfbmFtZQJ0bwxhY2NvdW50X25hbWUIcXVhbnRpdHkFYXNzZXQEbWVtbwZzdHJpbmcGY3JlYXRlAAIGaXNzdWVyDGFjY291bnRfbmFtZQ5tYXhpbXVtX3N1cHBseQVhc3NldAVpc3N1ZQADAnRvDGFjY291bnRfbmFtZQhxdWFudGl0eQVhc3NldARtZW1vBnN0cmluZwdhY2NvdW50AAEHYmFsYW5jZQVhc3NldA5jdXJyZW5jeV9zdGF0cwADBnN1cHBseQVhc3NldAptYXhfc3VwcGx5BWFzc2V0Bmlzc3VlcgxhY2NvdW50X25hbWUDAAAAVy08zc0IdHJhbnNmZXLnBSMjIFRyYW5zZmVyIFRlcm1zICYgQ29uZGl0aW9ucwoKSSwge3tmcm9tfX0sIGNlcnRpZnkgdGhlIGZvbGxvd2luZyB0byBiZSB0cnVlIHRvIHRoZSBiZXN0IG9mIG15IGtub3dsZWRnZToKCjEuIEkgY2VydGlmeSB0aGF0IHt7cXVhbnRpdHl9fSBpcyBub3QgdGhlIHByb2NlZWRzIG9mIGZyYXVkdWxlbnQgb3IgdmlvbGVudCBhY3Rpdml0aWVzLgoyLiBJIGNlcnRpZnkgdGhhdCwgdG8gdGhlIGJlc3Qgb2YgbXkga25vd2xlZGdlLCB7e3RvfX0gaXMgbm90IHN1cHBvcnRpbmcgaW5pdGlhdGlvbiBvZiB2aW9sZW5jZSBhZ2FpbnN0IG90aGVycy4KMy4gSSBoYXZlIGRpc2Nsb3NlZCBhbnkgY29udHJhY3R1YWwgdGVybXMgJiBjb25kaXRpb25zIHdpdGggcmVzcGVjdCB0byB7e3F1YW50aXR5fX0gdG8ge3t0b319LgoKSSB1bmRlcnN0YW5kIHRoYXQgZnVuZHMgdHJhbnNmZXJzIGFyZSBub3QgcmV2ZXJzaWJsZSBhZnRlciB0aGUge3t0cmFuc2FjdGlvbi5kZWxheX19IHNlY29uZHMgb3Igb3RoZXIgZGVsYXkgYXMgY29uZmlndXJlZCBieSB7e2Zyb219fSdzIHBlcm1pc3Npb25zLgoKSWYgdGhpcyBhY3Rpb24gZmFpbHMgdG8gYmUgaXJyZXZlcnNpYmx5IGNvbmZpcm1lZCBhZnRlciByZWNlaXZpbmcgZ29vZHMgb3Igc2VydmljZXMgZnJvbSAne3t0b319JywgSSBhZ3JlZSB0byBlaXRoZXIgcmV0dXJuIHRoZSBnb29kcyBvciBzZXJ2aWNlcyBvciByZXNlbmQge3txdWFudGl0eX19IGluIGEgdGltZWx5IG1hbm5lci4KAAAAAAClMXYFaXNzdWUAAAAAAKhs1EUGY3JlYXRlAAIAAAA4T00RMgNpNjQBCGN1cnJlbmN5AQZ1aW50NjQHYWNjb3VudAAAAAAAkE3GA2k2NAEIY3VycmVuY3kBBnVpbnQ2NA5jdXJyZW5jeV9zdGF0cwAAAA===', // eslint-disable-line
};
}
return transaction;
},
});
beforeEach(() => {
rpc = new JsonRpc('', { fetch });
const signatureProvider = new JsSignatureProvider(['5JtUScZK2XEp3g9gh7F8bwtPTRAkASmNrrftmx4AxDKD5K4zDnr']);
const chainId = '038f4b0fc8ff18a4f0842a8f0564611f6e96e8535901dd45e43ac8691a1c4dca';
api = new Api({
rpc,
signatureProvider,
chainId,
textDecoder: new TextDecoder(),
textEncoder: new TextEncoder()
});
});
it('Doesnt crash', () => {
expect(api).toBeTruthy();
});
it('getAbi returns an abi', async () => {
const response = await api.getAbi('tstzswcredit');
expect(response).toBeTruthy();
});
it('getTransactionAbis returns abis by transactions', async () => {
const response = await api.getTransactionAbis(transaction);
expect(response[0].abi.length).toBeGreaterThan(0);
});
it('getContract returns a contract', async () => {
const response = await api.getContract('tstzswcredit');
expect(response.actions).toBeTruthy();
});
it('deserializeTransaction converts tx from binary', () => {
const tx = api.deserializeTransaction(serializedTx);
expect(tx).toEqual(deserializedTx);
});
it('serializeActions converts actions to hex', async () => {
const response = await api.serializeActions(transaction.actions);
expect(response).toEqual(serializedActions);
});
it('deserializeActions converts actions from hex', async () => {
const response = await api.deserializeActions(serializedActions);
expect(response).toEqual(deserializedActions);
});
it('hasRequiredTaposFields returns true, if required fields are present', () => {
const response = api.hasRequiredTaposFields(transaction);
expect(response).toEqual(true);
});
it('rawAbiToJson returns correct Json from raw Abi', async () => {
const expected = await api.getAbi('tstzswcredit');
const response = await rpc.getRawAbi('tstzswcredit');
const actual = api.rawAbiToJson(response.abi);
expect(actual).toEqual(expected);
});
it('jsonToRawAbi returns correct raw Abi from Json', async () => {
const response = await rpc.getRawAbi('tstzswcredit');
const expected = response.abi;
const jsonAbi = await api.getAbi('tstzswcredit');
const actual = api.jsonToRawAbi(jsonAbi);
expect(actual).toEqual(expected);
});
it('confirms jsonToRawAbi and rawAbiToJson are reciprocal', async () => {
const expectedJsonAbi = await api.getAbi('tstzswcredit');
const response = await rpc.getRawAbi('tstzswcredit');
const expectedRawAbi = response.abi;
const jsonAbi = api.rawAbiToJson(api.jsonToRawAbi(expectedJsonAbi));
const rawAbi = api.jsonToRawAbi(api.rawAbiToJson(expectedRawAbi));
expect(rawAbi).toEqual(expectedRawAbi);
expect(jsonAbi).toEqual(expectedJsonAbi);
});
describe('Abi Serialization', () => {
const serializedAbis = {
'1.0': 'DmVvc2lvOjphYmkvMS4wAQxhY2NvdW50X25hbWUEbmFtZQUIdHJhbnNmZXIABARmcm9tDGFjY291bnRfbmFtZQJ0bwxhY2NvdW50X25hbWUIcXVhbnRpdHkFYXNzZXQEbWVtbwZzdHJpbmcGY3JlYXRlAAIGaXNzdWVyDGFjY291bnRfbmFtZQ5tYXhpbXVtX3N1cHBseQVhc3NldAVpc3N1ZQADAnRvDGFjY291bnRfbmFtZQhxdWFudGl0eQVhc3NldARtZW1vBnN0cmluZwdhY2NvdW50AAEHYmFsYW5jZQVhc3NldA5jdXJyZW5jeV9zdGF0cwADBnN1cHBseQVhc3NldAptYXhfc3VwcGx5BWFzc2V0Bmlzc3VlcgxhY2NvdW50X25hbWUDAAAAVy08zc0IdHJhbnNmZXLnBSMjIFRyYW5zZmVyIFRlcm1zICYgQ29uZGl0aW9ucwoKSSwge3tmcm9tfX0sIGNlcnRpZnkgdGhlIGZvbGxvd2luZyB0byBiZSB0cnVlIHRvIHRoZSBiZXN0IG9mIG15IGtub3dsZWRnZToKCjEuIEkgY2VydGlmeSB0aGF0IHt7cXVhbnRpdHl9fSBpcyBub3QgdGhlIHByb2NlZWRzIG9mIGZyYXVkdWxlbnQgb3IgdmlvbGVudCBhY3Rpdml0aWVzLgoyLiBJIGNlcnRpZnkgdGhhdCwgdG8gdGhlIGJlc3Qgb2YgbXkga25vd2xlZGdlLCB7e3RvfX0gaXMgbm90IHN1cHBvcnRpbmcgaW5pdGlhdGlvbiBvZiB2aW9sZW5jZSBhZ2FpbnN0IG90aGVycy4KMy4gSSBoYXZlIGRpc2Nsb3NlZCBhbnkgY29udHJhY3R1YWwgdGVybXMgJiBjb25kaXRpb25zIHdpdGggcmVzcGVjdCB0byB7e3F1YW50aXR5fX0gdG8ge3t0b319LgoKSSB1bmRlcnN0YW5kIHRoYXQgZnVuZHMgdHJhbnNmZXJzIGFyZSBub3QgcmV2ZXJzaWJsZSBhZnRlciB0aGUge3t0cmFuc2FjdGlvbi5kZWxheX19IHNlY29uZHMgb3Igb3RoZXIgZGVsYXkgYXMgY29uZmlndXJlZCBieSB7e2Zyb219fSdzIHBlcm1pc3Npb25zLgoKSWYgdGhpcyBhY3Rpb24gZmFpbHMgdG8gYmUgaXJyZXZlcnNpYmx5IGNvbmZpcm1lZCBhZnRlciByZWNlaXZpbmcgZ29vZHMgb3Igc2VydmljZXMgZnJvbSAne3t0b319JywgSSBhZ3JlZSB0byBlaXRoZXIgcmV0dXJuIHRoZSBnb29kcyBvciBzZXJ2aWNlcyBvciByZXNlbmQge3txdWFudGl0eX19IGluIGEgdGltZWx5IG1hbm5lci4KAAAAAAClMXYFaXNzdWUAAAAAAKhs1EUGY3JlYXRlAAIAAAA4T00RMgNpNjQBCGN1cnJlbmN5AQZ1aW50NjQHYWNjb3VudAAAAAAAkE3GA2k2NAEIY3VycmVuY3kBBnVpbnQ2NA5jdXJyZW5jeV9zdGF0cwAAAA===',
'1.1': 'DmVvc2lvOjphYmkvMS4xABgIYWJpX2hhc2gAAgVvd25lcgRuYW1lBGhhc2gLY2hlY2tzdW0yNTYIYWN0aXZhdGUAAQ5mZWF0dXJlX2RpZ2VzdAtjaGVja3N1bTI1NglhdXRob3JpdHkABAl0aHJlc2hvbGQGdWludDMyBGtleXMMa2V5X3dlaWdodFtdCGFjY291bnRzGXBlcm1pc3Npb25fbGV2ZWxfd2VpZ2h0W10Fd2FpdHMNd2FpdF93ZWlnaHRbXRVibG9ja2NoYWluX3BhcmFtZXRlcnMAERNtYXhfYmxvY2tfbmV0X3VzYWdlBnVpbnQ2NBp0YXJnZXRfYmxvY2tfbmV0X3VzYWdlX3BjdAZ1aW50MzIZbWF4X3RyYW5zYWN0aW9uX25ldF91c2FnZQZ1aW50MzIeYmFzZV9wZXJfdHJhbnNhY3Rpb25fbmV0X3VzYWdlBnVpbnQzMhBuZXRfdXNhZ2VfbGVld2F5BnVpbnQzMiNjb250ZXh0X2ZyZWVfZGlzY291bnRfbmV0X3VzYWdlX251bQZ1aW50MzIjY29udGV4dF9mcmVlX2Rpc2NvdW50X25ldF91c2FnZV9kZW4GdWludDMyE21heF9ibG9ja19jcHVfdXNhZ2UGdWludDMyGnRhcmdldF9ibG9ja19jcHVfdXNhZ2VfcGN0BnVpbnQzMhltYXhfdHJhbnNhY3Rpb25fY3B1X3VzYWdlBnVpbnQzMhltaW5fdHJhbnNhY3Rpb25fY3B1X3VzYWdlBnVpbnQzMhhtYXhfdHJhbnNhY3Rpb25fbGlmZXRpbWUGdWludDMyHmRlZmVycmVkX3RyeF9leHBpcmF0aW9uX3dpbmRvdwZ1aW50MzIVbWF4X3RyYW5zYWN0aW9uX2RlbGF5BnVpbnQzMhZtYXhfaW5saW5lX2FjdGlvbl9zaXplBnVpbnQzMhdtYXhfaW5saW5lX2FjdGlvbl9kZXB0aAZ1aW50MTYTbWF4X2F1dGhvcml0eV9kZXB0aAZ1aW50MTYLY2FuY2VsZGVsYXkAAg5jYW5jZWxpbmdfYXV0aBBwZXJtaXNzaW9uX2xldmVsBnRyeF9pZAtjaGVja3N1bTI1NgpkZWxldGVhdXRoAAIHYWNjb3VudARuYW1lCnBlcm1pc3Npb24EbmFtZQprZXlfd2VpZ2h0AAIDa2V5CnB1YmxpY19rZXkGd2VpZ2h0BnVpbnQxNghsaW5rYXV0aAAEB2FjY291bnQEbmFtZQRjb2RlBG5hbWUEdHlwZQRuYW1lC3JlcXVpcmVtZW50BG5hbWUKbmV3YWNjb3VudAAEB2NyZWF0b3IEbmFtZQRuYW1lBG5hbWUFb3duZXIJYXV0aG9yaXR5BmFjdGl2ZQlhdXRob3JpdHkHb25lcnJvcgACCXNlbmRlcl9pZAd1aW50MTI4CHNlbnRfdHJ4BWJ5dGVzEHBlcm1pc3Npb25fbGV2ZWwAAgVhY3RvcgRuYW1lCnBlcm1pc3Npb24EbmFtZRdwZXJtaXNzaW9uX2xldmVsX3dlaWdodAACCnBlcm1pc3Npb24QcGVybWlzc2lvbl9sZXZlbAZ3ZWlnaHQGdWludDE2DHByb2R1Y2VyX2tleQACDXByb2R1Y2VyX25hbWUEbmFtZRFibG9ja19zaWduaW5nX2tleQpwdWJsaWNfa2V5DHJlcWFjdGl2YXRlZAABDmZlYXR1cmVfZGlnZXN0C2NoZWNrc3VtMjU2B3JlcWF1dGgAAQRmcm9tBG5hbWUGc2V0YWJpAAIHYWNjb3VudARuYW1lA2FiaQVieXRlcwpzZXRhbGltaXRzAAQHYWNjb3VudARuYW1lCXJhbV9ieXRlcwVpbnQ2NApuZXRfd2VpZ2h0BWludDY0CmNwdV93ZWlnaHQFaW50NjQHc2V0Y29kZQAEB2FjY291bnQEbmFtZQZ2bXR5cGUFdWludDgJdm12ZXJzaW9uBXVpbnQ4BGNvZGUFYnl0ZXMJc2V0cGFyYW1zAAEGcGFyYW1zFWJsb2NrY2hhaW5fcGFyYW1ldGVycwdzZXRwcml2AAIHYWNjb3VudARuYW1lB2lzX3ByaXYFdWludDgIc2V0cHJvZHMAAQhzY2hlZHVsZQ5wcm9kdWNlcl9rZXlbXQp1bmxpbmthdXRoAAMHYWNjb3VudARuYW1lBGNvZGUEbmFtZQR0eXBlBG5hbWUKdXBkYXRlYXV0aAAEB2FjY291bnQEbmFtZQpwZXJtaXNzaW9uBG5hbWUGcGFyZW50BG5hbWUEYXV0aAlhdXRob3JpdHkLd2FpdF93ZWlnaHQAAgh3YWl0X3NlYwZ1aW50MzIGd2VpZ2h0BnVpbnQxNhAAAAAqm+0yMghhY3RpdmF0Zd8CLS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogQWN0aXZhdGUgUHJvdG9jb2wgRmVhdHVyZQpzdW1tYXJ5OiAnQWN0aXZhdGUgcHJvdG9jb2wgZmVhdHVyZSB7e25vd3JhcCBmZWF0dXJlX2RpZ2VzdH19JwppY29uOiBodHRwOi8vMTI3LjAuMC4xL3JpY2FyZGlhbl9hc3NldHMvZW9zaW8uY29udHJhY3RzL2ljb25zL2FkbWluLnBuZyM5YmYxY2VjNjY0ODYzYmQ2YWFhYzBmODE0YjIzNWY4Nzk5ZmIwMmM4NTBlOWFhNWRhMzRlOGEwMDRiZDY1MThlCi0tLQoKe3skYWN0aW9uLmFjY291bnR9fSBhY3RpdmF0ZXMgdGhlIHByb3RvY29sIGZlYXR1cmUgd2l0aCBhIGRpZ2VzdCBvZiB7e2ZlYXR1cmVfZGlnZXN0fX0uALyJKkWFpkELY2FuY2VsZGVsYXnhAi0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IENhbmNlbCBEZWxheWVkIFRyYW5zYWN0aW9uCnN1bW1hcnk6ICd7e25vd3JhcCBjYW5jZWxpbmdfYXV0aC5hY3Rvcn19IGNhbmNlbHMgYSBkZWxheWVkIHRyYW5zYWN0aW9uJwppY29uOiBodHRwOi8vMTI3LjAuMC4xL3JpY2FyZGlhbl9hc3NldHMvZW9zaW8uY29udHJhY3RzL2ljb25zL2FjY291bnQucG5nIzNkNTVhMmZjM2E1YzIwYjQ1NmY1NjU3ZmFmNjY2YmMyNWZmZDA2ZjQ4MzZjNWU4MjU2Zjc0MTE0OWIwYjI5NGYKLS0tCgp7e2NhbmNlbGluZ19hdXRoLmFjdG9yfX0gY2FuY2VscyB0aGUgZGVsYXllZCB0cmFuc2FjdGlvbiB3aXRoIGlkIHt7dHJ4X2lkfX0uAEDL2qisokoKZGVsZXRlYXV0aMwCLS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogRGVsZXRlIEFjY291bnQgUGVybWlzc2lvbgpzdW1tYXJ5OiAnRGVsZXRlIHRoZSB7e25vd3JhcCBwZXJtaXNzaW9ufX0gcGVybWlzc2lvbiBvZiB7e25vd3JhcCBhY2NvdW50fX0nCmljb246IGh0dHA6Ly8xMjcuMC4wLjEvcmljYXJkaWFuX2Fzc2V0cy9lb3Npby5jb250cmFjdHMvaWNvbnMvYWNjb3VudC5wbmcjM2Q1NWEyZmMzYTVjMjBiNDU2ZjU2NTdmYWY2NjZiYzI1ZmZkMDZmNDgzNmM1ZTgyNTZmNzQxMTQ5YjBiMjk0ZgotLS0KCkRlbGV0ZSB0aGUge3twZXJtaXNzaW9ufX0gcGVybWlzc2lvbiBvZiB7e2FjY291bnR9fS4AAAAtawOniwhsaW5rYXV0aPQELS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogTGluayBBY3Rpb24gdG8gUGVybWlzc2lvbgpzdW1tYXJ5OiAne3tub3dyYXAgYWNjb3VudH19IHNldHMgdGhlIG1pbmltdW0gcmVxdWlyZWQgcGVybWlzc2lvbiBmb3IgdGhlIHt7I2lmIHR5cGV9fXt7bm93cmFwIHR5cGV9fSBhY3Rpb24gb2YgdGhle3svaWZ9fSB7e25vd3JhcCBjb2RlfX0gY29udHJhY3QgdG8ge3tub3dyYXAgcmVxdWlyZW1lbnR9fScKaWNvbjogaHR0cDovLzEyNy4wLjAuMS9yaWNhcmRpYW5fYXNzZXRzL2Vvc2lvLmNvbnRyYWN0cy9pY29ucy9hY2NvdW50LnBuZyMzZDU1YTJmYzNhNWMyMGI0NTZmNTY1N2ZhZjY2NmJjMjVmZmQwNmY0ODM2YzVlODI1NmY3NDExNDliMGIyOTRmCi0tLQoKe3thY2NvdW50fX0gc2V0cyB0aGUgbWluaW11bSByZXF1aXJlZCBwZXJtaXNzaW9uIGZvciB0aGUge3sjaWYgdHlwZX19e3t0eXBlfX0gYWN0aW9uIG9mIHRoZXt7L2lmfX0ge3tjb2RlfX0gY29udHJhY3QgdG8ge3tyZXF1aXJlbWVudH19LgoKe3sjaWYgdHlwZX19e3tlbHNlfX1BbnkgbGlua3MgZXhwbGljaXRseSBhc3NvY2lhdGVkIHRvIHNwZWNpZmljIGFjdGlvbnMgb2Yge3tjb2RlfX0gd2lsbCB0YWtlIHByZWNlZGVuY2Uue3svaWZ9fQBAnpoiZLiaCm5ld2FjY291bnTXAy0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IENyZWF0ZSBOZXcgQWNjb3VudApzdW1tYXJ5OiAne3tub3dyYXAgY3JlYXRvcn19IGNyZWF0ZXMgYSBuZXcgYWNjb3VudCB3aXRoIHRoZSBuYW1lIHt7bm93cmFwIG5hbWV9fScKaWNvbjogaHR0cDovLzEyNy4wLjAuMS9yaWNhcmRpYW5fYXNzZXRzL2Vvc2lvLmNvbnRyYWN0cy9pY29ucy9hY2NvdW50LnBuZyMzZDU1YTJmYzNhNWMyMGI0NTZmNTY1N2ZhZjY2NmJjMjVmZmQwNmY0ODM2YzVlODI1NmY3NDExNDliMGIyOTRmCi0tLQoKe3tjcmVhdG9yfX0gY3JlYXRlcyBhIG5ldyBhY2NvdW50IHdpdGggdGhlIG5hbWUge3tuYW1lfX0gYW5kIHRoZSBmb2xsb3dpbmcgcGVybWlzc2lvbnM6Cgpvd25lciBwZXJtaXNzaW9uIHdpdGggYXV0aG9yaXR5Ogp7e3RvX2pzb24gb3duZXJ9fQoKYWN0aXZlIHBlcm1pc3Npb24gd2l0aCBhdXRob3JpdHk6Cnt7dG9fanNvbiBhY3RpdmV9fQAAAODSe9WkB29uZXJyb3IAkFQ222VkrLoMcmVxYWN0aXZhdGVk/wItLS0Kc3BlY192ZXJzaW9uOiAiMC4yLjAiCnRpdGxlOiBBc3NlcnQgUHJvdG9jb2wgRmVhdHVyZSBBY3RpdmF0aW9uCnN1bW1hcnk6ICdBc3NlcnQgdGhhdCBwcm90b2NvbCBmZWF0dXJlIHt7bm93cmFwIGZlYXR1cmVfZGlnZXN0fX0gaGFzIGJlZW4gYWN0aXZhdGVkJwppY29uOiBodHRwOi8vMTI3LjAuMC4xL3JpY2FyZGlhbl9hc3NldHMvZW9zaW8uY29udHJhY3RzL2ljb25zL2FkbWluLnBuZyM5YmYxY2VjNjY0ODYzYmQ2YWFhYzBmODE0YjIzNWY4Nzk5ZmIwMmM4NTBlOWFhNWRhMzRlOGEwMDRiZDY1MThlCi0tLQoKQXNzZXJ0IHRoYXQgdGhlIHByb3RvY29sIGZlYXR1cmUgd2l0aCBhIGRpZ2VzdCBvZiB7e2ZlYXR1cmVfZGlnZXN0fX0gaGFzIGJlZW4gYWN0aXZhdGVkLgAAAKBlbay6B3JlcWF1dGi8Ai0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IEFzc2VydCBBdXRob3JpemF0aW9uCnN1bW1hcnk6ICdBc3NlcnQgdGhhdCBhdXRob3JpemF0aW9uIGJ5IHt7bm93cmFwIGZyb219fSBpcyBwcm92aWRlZCcKaWNvbjogaHR0cDovLzEyNy4wLjAuMS9yaWNhcmRpYW5fYXNzZXRzL2Vvc2lvLmNvbnRyYWN0cy9pY29ucy9hY2NvdW50LnBuZyMzZDU1YTJmYzNhNWMyMGI0NTZmNTY1N2ZhZjY2NmJjMjVmZmQwNmY0ODM2YzVlODI1NmY3NDExNDliMGIyOTRmCi0tLQoKQXNzZXJ0IHRoYXQgYXV0aG9yaXphdGlvbiBieSB7e2Zyb219fSBpcyBwcm92aWRlZC4AAAAAuGOywgZzZXRhYmnKAi0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IERlcGxveSBDb250cmFjdCBBQkkKc3VtbWFyeTogJ0RlcGxveSBjb250cmFjdCBBQkkgb24gYWNjb3VudCB7e25vd3JhcCBhY2NvdW50fX0nCmljb246IGh0dHA6Ly8xMjcuMC4wLjEvcmljYXJkaWFuX2Fzc2V0cy9lb3Npby5jb250cmFjdHMvaWNvbnMvYWNjb3VudC5wbmcjM2Q1NWEyZmMzYTVjMjBiNDU2ZjU2NTdmYWY2NjZiYzI1ZmZkMDZmNDgzNmM1ZTgyNTZmNzQxMTQ5YjBiMjk0ZgotLS0KCkRlcGxveSB0aGUgQUJJIGZpbGUgYXNzb2NpYXRlZCB3aXRoIHRoZSBjb250cmFjdCBvbiBhY2NvdW50IHt7YWNjb3VudH19LgAAzk66aLLCCnNldGFsaW1pdHPNAy0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IEFkanVzdCBSZXNvdXJjZSBMaW1pdHMgb2YgQWNjb3VudApzdW1tYXJ5OiAnQWRqdXN0IHJlc291cmNlIGxpbWl0cyBvZiBhY2NvdW50IHt7bm93cmFwIGFjY291bnR9fScKaWNvbjogaHR0cDovLzEyNy4wLjAuMS9yaWNhcmRpYW5fYXNzZXRzL2Vvc2lvLmNvbnRyYWN0cy9pY29ucy9hZG1pbi5wbmcjOWJmMWNlYzY2NDg2M2JkNmFhYWMwZjgxNGIyMzVmODc5OWZiMDJjODUwZTlhYTVkYTM0ZThhMDA0YmQ2NTE4ZQotLS0KCnt7JGFjdGlvbi5hY2NvdW50fX0gdXBkYXRlcyB7e2FjY291bnR9feKAmXMgcmVzb3VyY2UgbGltaXRzIHRvIGhhdmUgYSBSQU0gcXVvdGEgb2Yge3tyYW1fYnl0ZXN9fSBieXRlcywgYSBORVQgYmFuZHdpZHRoIHF1b3RhIG9mIHt7bmV0X3dlaWdodH19IGFuZCBhIENQVSBiYW5kd2lkdGggcXVvdGEgb2Yge3tjcHVfd2VpZ2h0fX0uAAAAQCWKssIHc2V0Y29kZb0CLS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogRGVwbG95IENvbnRyYWN0IENvZGUKc3VtbWFyeTogJ0RlcGxveSBjb250cmFjdCBjb2RlIG9uIGFjY291bnQge3tub3dyYXAgYWNjb3VudH19JwppY29uOiBodHRwOi8vMTI3LjAuMC4xL3JpY2FyZGlhbl9hc3NldHMvZW9zaW8uY29udHJhY3RzL2ljb25zL2FjY291bnQucG5nIzNkNTVhMmZjM2E1YzIwYjQ1NmY1NjU3ZmFmNjY2YmMyNWZmZDA2ZjQ4MzZjNWU4MjU2Zjc0MTE0OWIwYjI5NGYKLS0tCgpEZXBsb3kgY29tcGlsZWQgY29udHJhY3QgY29kZSB0byB0aGUgYWNjb3VudCB7e2FjY291bnR9fS4AAMDSXFOzwglzZXRwYXJhbXOnAi0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IFNldCBTeXN0ZW0gUGFyYW1ldGVycwpzdW1tYXJ5OiAnU2V0IHN5c3RlbSBwYXJhbWV0ZXJzJwppY29uOiBodHRwOi8vMTI3LjAuMC4xL3JpY2FyZGlhbl9hc3NldHMvZW9zaW8uY29udHJhY3RzL2ljb25zL2FkbWluLnBuZyM5YmYxY2VjNjY0ODYzYmQ2YWFhYzBmODE0YjIzNWY4Nzk5ZmIwMmM4NTBlOWFhNWRhMzRlOGEwMDRiZDY1MThlCi0tLQoKe3skYWN0aW9uLmFjY291bnR9fSBzZXRzIHN5c3RlbSBwYXJhbWV0ZXJzIHRvOgp7e3RvX2pzb24gcGFyYW1zfX0AAABgu1uzwgdzZXRwcml25AMtLS0Kc3BlY192ZXJzaW9uOiAiMC4yLjAiCnRpdGxlOiBNYWtlIGFuIEFjY291bnQgUHJpdmlsZWdlZCBvciBVbnByaXZpbGVnZWQKc3VtbWFyeTogJ3t7I2lmIGlzX3ByaXZ9fU1ha2Uge3tub3dyYXAgYWNjb3VudH19IHByaXZpbGVnZWR7e2Vsc2V9fVJlbW92ZSBwcml2aWxlZ2VkIHN0YXR1cyBvZiB7e25vd3JhcCBhY2NvdW50fX17ey9pZn19JwppY29uOiBodHRwOi8vMTI3LjAuMC4xL3JpY2FyZGlhbl9hc3NldHMvZW9zaW8uY29udHJhY3RzL2ljb25zL2FkbWluLnBuZyM5YmYxY2VjNjY0ODYzYmQ2YWFhYzBmODE0YjIzNWY4Nzk5ZmIwMmM4NTBlOWFhNWRhMzRlOGEwMDRiZDY1MThlCi0tLQoKe3sjaWYgaXNfcHJpdn19Cnt7JGFjdGlvbi5hY2NvdW50fX0gbWFrZXMge3thY2NvdW50fX0gcHJpdmlsZWdlZC4Ke3tlbHNlfX0Ke3skYWN0aW9uLmFjY291bnR9fSByZW1vdmVzIHByaXZpbGVnZWQgc3RhdHVzIG9mIHt7YWNjb3VudH19Lgp7ey9pZn19AAAAONFbs8IIc2V0cHJvZHOUAy0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IFNldCBCbG9jayBQcm9kdWNlcnMKc3VtbWFyeTogJ1NldCBibG9jayBwcm9kdWNlciBzY2hlZHVsZScKaWNvbjogaHR0cDovLzEyNy4wLjAuMS9yaWNhcmRpYW5fYXNzZXRzL2Vvc2lvLmNvbnRyYWN0cy9pY29ucy9hZG1pbi5wbmcjOWJmMWNlYzY2NDg2M2JkNmFhYWMwZjgxNGIyMzVmODc5OWZiMDJjODUwZTlhYTVkYTM0ZThhMDA0YmQ2NTE4ZQotLS0KCnt7JGFjdGlvbi5hY2NvdW50fX0gcHJvcG9zZXMgYSBibG9jayBwcm9kdWNlciBzY2hlZHVsZSBvZjoKe3sjZWFjaCBzY2hlZHVsZX19CiAgMS4ge3t0aGlzLnByb2R1Y2VyX25hbWV9fSB3aXRoIGEgYmxvY2sgc2lnbmluZyBrZXkgb2Yge3t0aGlzLmJsb2NrX3NpZ25pbmdfa2V5fX0Ke3svZWFjaH19AEDL2sDp4tQKdW5saW5rYXV0aOgELS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogVW5saW5rIEFjdGlvbiBmcm9tIFBlcm1pc3Npb24Kc3VtbWFyeTogJ3t7bm93cmFwIGFjY291bnR9fSB1bnNldHMgdGhlIG1pbmltdW0gcmVxdWlyZWQgcGVybWlzc2lvbiBmb3IgdGhlIHt7I2lmIHR5cGV9fXt7bm93cmFwIHR5cGV9fSBhY3Rpb24gb2YgdGhle3svaWZ9fSB7e25vd3JhcCBjb2RlfX0gY29udHJhY3QnCmljb246IGh0dHA6Ly8xMjcuMC4wLjEvcmljYXJkaWFuX2Fzc2V0cy9lb3Npby5jb250cmFjdHMvaWNvbnMvYWNjb3VudC5wbmcjM2Q1NWEyZmMzYTVjMjBiNDU2ZjU2NTdmYWY2NjZiYzI1ZmZkMDZmNDgzNmM1ZTgyNTZmNzQxMTQ5YjBiMjk0ZgotLS0KCnt7YWNjb3VudH19IHJlbW92ZXMgdGhlIGFzc29jaWF0aW9uIGJldHdlZW4gdGhlIHt7I2lmIHR5cGV9fXt7dHlwZX19IGFjdGlvbiBvZiB0aGV7ey9pZn19IHt7Y29kZX19IGNvbnRyYWN0IGFuZCBpdHMgbWluaW11bSByZXF1aXJlZCBwZXJtaXNzaW9uLgoKe3sjaWYgdHlwZX19e3tlbHNlfX1UaGlzIHdpbGwgbm90IHJlbW92ZSBhbnkgbGlua3MgZXhwbGljaXRseSBhc3NvY2lhdGVkIHRvIHNwZWNpZmljIGFjdGlvbnMgb2Yge3tjb2RlfX0ue3svaWZ9fQBAy9qobFLVCnVwZGF0ZWF1dGjEAy0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IE1vZGlmeSBBY2NvdW50IFBlcm1pc3Npb24Kc3VtbWFyeTogJ0FkZCBvciB1cGRhdGUgdGhlIHt7bm93cmFwIHBlcm1pc3Npb259fSBwZXJtaXNzaW9uIG9mIHt7bm93cmFwIGFjY291bnR9fScKaWNvbjogaHR0cDovLzEyNy4wLjAuMS9yaWNhcmRpYW5fYXNzZXRzL2Vvc2lvLmNvbnRyYWN0cy9pY29ucy9hY2NvdW50LnBuZyMzZDU1YTJmYzNhNWMyMGI0NTZmNTY1N2ZhZjY2NmJjMjVmZmQwNmY0ODM2YzVlODI1NmY3NDExNDliMGIyOTRmCi0tLQoKTW9kaWZ5LCBhbmQgY3JlYXRlIGlmIG5lY2Vzc2FyeSwgdGhlIHt7cGVybWlzc2lvbn19IHBlcm1pc3Npb24gb2Yge3thY2NvdW50fX0gdG8gaGF2ZSBhIHBhcmVudCBwZXJtaXNzaW9uIG9mIHt7cGFyZW50fX0gYW5kIHRoZSBmb2xsb3dpbmcgYXV0aG9yaXR5Ogp7e3RvX2pzb24gYXV0aH19AQAAAKBh09wxA2k2NAAACGFiaV9oYXNoAAAAAAAA=',
'1.2': 'DmVvc2lvOjphYmkvMS4yAAcDZGVsAAEEdXVpZAZzdHJpbmcMZ2V0YnlhY2NuYW1lAAEMYWNjb3VudF9uYW1lBG5hbWUKdG9kb19lbnRyeQAEBHV1aWQGc3RyaW5nDGFjY291bnRfbmFtZRF0dXBsZV9zdHJpbmdfbmFtZQR0YXNrE3R1cGxlX3N0cmluZ19zdHJpbmcHY2hlY2tlZBF0dXBsZV9zdHJpbmdfYm9vbBF0dXBsZV9zdHJpbmdfYm9vbAACB2ZpZWxkXzAGc3RyaW5nB2ZpZWxkXzEEYm9vbBF0dXBsZV9zdHJpbmdfbmFtZQACB2ZpZWxkXzAGc3RyaW5nB2ZpZWxkXzEEbmFtZRN0dXBsZV9zdHJpbmdfc3RyaW5nAAIHZmllbGRfMAZzdHJpbmcHZmllbGRfMQZzdHJpbmcGdXBzZXJ0AAQEdXVpZAZzdHJpbmcMYWNjb3VudF9uYW1lBG5hbWUEdGFzawZzdHJpbmcHY2hlY2tlZARib29sAwAAAAAAAKJKA2RlbACgpJkIGX+yYgxnZXRieWFjY25hbWUAAAAAAOSrcNUGdXBzZXJ0AAAAAAAAAqCkmQgZf7JiDHRvZG9fZW50cnlbXQAAAADkq3DVCnRvZG9fZW50cnkBAAAAANBE84YKdG9kb19lbnRyeQAAAAAAkJzWBnN0cmluZwMAAABASTMRMhF0dXBsZV9zdHJpbmdfbmFtZQAAACApiFRDEXR1cGxlX3N0cmluZ19ib29sAAAAAAAAsckTdHVwbGVfc3RyaW5nX3N0cmluZw===',
};
it('deserializes/serializes the 1.0 abi', () => {
const raw = base64ToBinary(serializedAbis['1.0']);
const deserializedAbi = api.rawAbiToJson(raw);
const serializedAbi = api.jsonToRawAbi(deserializedAbi);
expect(serializedAbi).toEqual(raw);
});
it('deserializes/serializes the 1.1 abi', () => {
const raw = base64ToBinary(serializedAbis['1.1']);
const deserializedAbi = api.rawAbiToJson(raw);
const serializedAbi = api.jsonToRawAbi(deserializedAbi);
expect(serializedAbi).toEqual(raw);
});
it('deserializes/serializes the 1.2 abi', () => {
const raw = base64ToBinary(serializedAbis['1.2']);
const deserializedAbi = api.rawAbiToJson(raw);
const serializedAbi = api.jsonToRawAbi(deserializedAbi);
expect(serializedAbi).toEqual(raw);
});
});
describe('Api shorthand design (JsonAbi)', () => {
it('errors if abi is not cached', () => {
const abiCheck = () => {
api.with('tstzswcredit').as('bob')
.transfer('thegazelle', 'remasteryoda', '1.0000 ZSWCC', 'For a secure future.');
};
expect(abiCheck).toThrowError('ABI must be cached before using ActionBuilder, run api.getAbi()');
});
it('generates a valid serialized action using api.with()', async () => {
await api.getAbi('tstzswcredit');
const serializedAction = api.with('tstzswcredit').as('thegazelle')
.transfer('thegazelle', 'remasteryoda', '1.0000 ZSWCC', 'For a secure future.');
expect(serializedAction).toEqual(serializedActions[0]);
});
it('generates a valid serialized action using tx.with()', async () => {
await api.getAbi('tstzswcredit');
const tx = api.buildTransaction();
const serializedAction = tx.with('tstzswcredit').as('thegazelle')
.transfer('thegazelle', 'remasteryoda', '2.0000 ZSWCC', 'For a second secure future (multiverse?)');
expect(serializedAction).toEqual(serializedActions[1]);
});
it('confirms serializeActions and ActionBuilder return same serialized data', async () => {
const response = await api.serializeActions(transaction.actions);
const firstAction = api.with('tstzswcredit').as('thegazelle')
.transfer('thegazelle', 'remasteryoda', '1.0000 ZSWCC', 'For a secure future.');
const secondAction = api.with('tstzswcredit').as('thegazelle')
.transfer('thegazelle', 'remasteryoda', '2.0000 ZSWCC', 'For a second secure future (multiverse?)');
expect([firstAction, secondAction]).toEqual(response);
});
it('generates the same serialized data using the longer authorization', async () => {
await api.getAbi('tstzswcredit');
const firstSerializedAction =
api.with('tstzswcredit').as('thegazelle')
.transfer('thegazelle', 'remasteryoda', '1.0000 ZSWCC', 'For a secure future.');
const secondSerializedAction =
api.with('tstzswcredit').as([{ actor: 'thegazelle', permission: 'active'}])
.transfer('thegazelle', 'remasteryoda', '1.0000 ZSWCC', 'For a secure future.');
expect(firstSerializedAction).toEqual(secondSerializedAction);
});
it('confirms the transaction extension serialization is reciprocal', async () => {
const resourcePayerTrx = {
expiration: '2021-06-28T15:55:37.000',
ref_block_num: 2097,
ref_block_prefix: 1309445478,
actions: [{
account: 'zsw.token',
name: 'transfer',
authorization: [{
actor: 'bob',
permission: 'active',
}, {
actor: 'alice',
permission: 'active',
}],
data: '0000000000000E3D0000000000855C34010000000000000004535953000000000E7265736F75726365207061796572'
}],
context_free_actions: [] as any,
resource_payer: {
payer: 'payer',
max_net_bytes: 4096,
max_cpu_us: 250,
max_memory_bytes: 0
}
};
const serialized = [[1, '0000000080ABBCA90010000000000000FA000000000000000000000000000000']];
const deserialized = {
resource_payer: {
payer: 'payer',
max_net_bytes: 4096,
max_cpu_us: 250,
max_memory_bytes: 0
}
};
const serializedTransactionExtensions = api.serializeTransactionExtensions(resourcePayerTrx);
expect(serializedTransactionExtensions).toEqual(serialized);
const deserializedTransactionExtensions = api.deserializeTransactionExtensions(serialized);
expect(deserializedTransactionExtensions).toEqual(deserialized);
});
});
});
import { JsonRpc } from '../zswjs-jsonrpc';
import { RpcError } from '../zswjs-rpcerror';
describe('JSON RPC', () => {
const endpointExtraSlash = 'http://localhost/';
const endpoint = 'http://localhost';
const fetchMock = fetch as any;
let jsonRpc: JsonRpc;
beforeEach(() => {
fetchMock.resetMocks();
jsonRpc = new JsonRpc(endpointExtraSlash);
});
it('throws error bad status', async () => {
let actMessage = '';
const expMessage = 'Not Found';
const accountName = 'myaccountaaa';
const expReturn = { data: '12345', message: expMessage };
fetchMock.once(JSON.stringify(expReturn), { status: 404 });
// async / await don't play well with expect().toThrow()
try {
await jsonRpc.get_abi(accountName);
} catch (e) {
expect(e).toBeInstanceOf(RpcError);
actMessage = e.message;
}
expect(actMessage).toEqual(expMessage);
});
it('throws error unprocessed', async () => {
let actMessage = '';
const expMessage = 'Not Processed';
const accountName = 'myaccountaaa';
const expReturn = {
data: '12345',
processed: {
except: {
message: expMessage,
},
},
};
fetchMock.once(JSON.stringify(expReturn));
try {
await jsonRpc.get_abi(accountName);
} catch (e) {
expect(e).toBeInstanceOf(RpcError);
actMessage = e.message;
}
expect(actMessage).toEqual(expMessage);
});
it('calls provided fetch instead of default', async () => {
const expPath = '/v1/chain/get_abi';
const accountName = 'myaccountaaa';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
account_name: accountName,
}),
method: 'POST',
};
const mockResp = {
json: () => {
return expReturn;
},
ok: true,
};
const myFetch = jest.fn();
myFetch.mockReturnValue(mockResp);
jsonRpc = new JsonRpc(endpoint, { fetch: myFetch });
await jsonRpc.get_abi(accountName);
expect(myFetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_abi', async () => {
const expPath = '/v1/chain/get_abi';
const accountName = 'myaccountaaa';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
account_name: accountName,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_abi(accountName);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_account', async () => {
const expPath = '/v1/chain/get_account';
const accountName = 'myaccountaaa';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
account_name: accountName,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_account(accountName);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_block_header_state', async () => {
const expPath = '/v1/chain/get_block_header_state';
const blockNumOrId = 1234;
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
block_num_or_id: blockNumOrId,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_block_header_state(blockNumOrId);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_block_info', async () => {
const expPath = '/v1/chain/get_block_info';
const blockNum = 1234;
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
block_num: blockNum,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_block_info(blockNum);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_block', async () => {
const expPath = '/v1/chain/get_block';
const blockNumOrId = 1234;
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
block_num_or_id: blockNumOrId,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_block(blockNumOrId);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_code', async () => {
const expPath = '/v1/chain/get_code';
const accountName = 'myaccountaaa';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
account_name: accountName,
code_as_wasm: true,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_code(accountName);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_currency_balance with all params', async () => {
const expPath = '/v1/chain/get_currency_balance';
const code = 'morse';
const account = 'myaccountaaa';
const symbol = 'ZSW';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
code,
account,
symbol,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_currency_balance(code, account, symbol);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_currency_balance with default params', async () => {
const expPath = '/v1/chain/get_currency_balance';
const code = 'morse';
const account = 'myaccountaaa';
const symbol: string = null;
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
code,
account,
symbol,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_currency_balance(code, account);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_currency_stats with all params', async () => {
const expPath = '/v1/chain/get_currency_stats';
const code = 'morse';
const symbol = 'ZSW';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
code,
symbol,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_currency_stats(code, symbol);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_info', async () => {
const expPath = '/v1/chain/get_info';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_info();
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_producer_schedule', async () => {
const expPath = '/v1/chain/get_producer_schedule';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_producer_schedule();
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_producers with all params', async () => {
const expPath = '/v1/chain/get_producers';
const json = false;
const lowerBound = 'zero';
const limit = 10;
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
json,
lower_bound: lowerBound,
limit,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_producers(json, lowerBound, limit);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_producers with default params', async () => {
const expPath = '/v1/chain/get_producers';
const json = true;
const lowerBound = '';
const limit = 50;
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
json,
lower_bound: lowerBound,
limit,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_producers();
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_raw_code_and_abi', async () => {
const expPath = '/v1/chain/get_raw_code_and_abi';
const accountName = 'myaccountaaa';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
account_name: accountName,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_raw_code_and_abi(accountName);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_scheduled_transactions', async () => {
const expPath = '/v1/chain/get_scheduled_transactions';
const json = true;
const lowerBound = '';
const limit = 50;
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
json,
lower_bound: lowerBound,
limit,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_scheduled_transactions();
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_table_rows with all params', async () => {
const expPath = '/v1/chain/get_table_rows';
const json = false;
const code = 'morse';
const scope = 'minty';
const table = 'coffee';
const lowerBound = 'zero';
const upperBound = 'five';
const limit = 20;
const indexPosition = 2;
const keyType = 'str';
const expReturn = { data: '12345' };
const reverse = false;
const showPayer = false;
const callParams = {
json,
code,
scope,
table,
lower_bound: lowerBound,
upper_bound: upperBound,
index_position: indexPosition,
key_type: keyType,
limit,
reverse,
show_payer: showPayer,
};
const expParams = {
body: JSON.stringify(callParams),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_table_rows(callParams);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_table_rows with default params', async () => {
const expPath = '/v1/chain/get_table_rows';
const json = true;
const code = 'morse';
const scope = 'minty';
const table = 'coffee';
const lowerBound = '';
const upperBound = '';
const limit = 10;
const indexPosition = 1;
const keyType = '';
const reverse = false;
const showPayer = false;
const expReturn = { data: '12345' };
const callParams = {
code,
scope,
table,
};
const expParams = {
body: JSON.stringify({
json,
code,
scope,
table,
lower_bound: lowerBound,
upper_bound: upperBound,
index_position: indexPosition,
key_type: keyType,
limit,
reverse,
show_payer: showPayer,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_table_rows(callParams);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_kv_table_rows with all params', async () => {
const expPath = '/v1/chain/get_kv_table_rows';
const json = false;
const code = 'morse';
const table = 'coffee';
const indexName = 'type';
const encodeType = 'bytes';
const indexValue = 'minty';
const lowerBound = 'zero';
const upperBound = 'five';
const limit = 10;
const reverse = false;
const showPayer = false;
const expReturn = { data: '12345' };
const callParams = {
json,
code,
table,
index_name: indexName,
encode_type: encodeType,
index_value: indexValue,
lower_bound: lowerBound,
upper_bound: upperBound,
limit,
reverse,
show_payer: showPayer,
};
const expParams = {
body: JSON.stringify(callParams),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_kv_table_rows(callParams);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_kv_table_rows with default params', async () => {
const expPath = '/v1/chain/get_kv_table_rows';
const json = true;
const code = 'morse';
const table = 'coffee';
const indexName = 'type';
const encodeType = 'bytes';
const limit = 10;
const reverse = false;
const showPayer = false;
const expReturn = { data: '12345' };
const callParams = {
code,
table,
index_name: indexName,
};
const expParams = {
body: JSON.stringify({
json,
code,
table,
index_name: indexName,
encode_type: encodeType,
limit,
reverse,
show_payer: showPayer,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_kv_table_rows(callParams);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_table_by_scope with all params', async () => {
const expPath = '/v1/chain/get_table_by_scope';
const code = 'morse';
const table = 'coffee';
const lowerBound = 'minty';
const upperBound = 'minty';
const limit = 20;
const expReturn = { data: '12345' };
const callParams = {
code,
table,
lower_bound: lowerBound,
upper_bound: upperBound,
limit,
};
const expParams = {
body: JSON.stringify(callParams),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_table_by_scope(callParams);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls get_table_by_scope with default params', async () => {
const expPath = '/v1/chain/get_table_by_scope';
const code = 'morse';
const table = 'coffee';
const lowerBound = '';
const upperBound = '';
const limit = 10;
const expReturn = { data: '12345' };
const callParams = {
code,
table,
};
const expParams = {
body: JSON.stringify({
code,
table,
lower_bound: lowerBound,
upper_bound: upperBound,
limit,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.get_table_by_scope(callParams);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls getRequiredKeys', async () => {
const expPath = '/v1/chain/get_required_keys';
const keys = ['key1', 'key2', 'key3'];
const expReturn = { required_keys: keys };
const callParams = {
transaction: 'mytxn',
availableKeys: keys,
};
const expParams = {
body: JSON.stringify({
transaction: callParams.transaction,
available_keys: callParams.availableKeys,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.getRequiredKeys(callParams);
expect(response).toEqual(expReturn.required_keys);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls push_transaction', async () => {
const expPath = '/v1/chain/push_transaction';
const signatures = [
'George Washington',
'John Hancock',
'Abraham Lincoln',
];
const serializedTransaction = new Uint8Array([
0, 16, 32, 128, 255,
]);
const limit = 50;
const expReturn = { data: '12345' };
const callParams = {
signatures,
serializedTransaction,
};
const expParams = {
body: JSON.stringify({
signatures,
compression: 0,
packed_context_free_data: '',
packed_trx: '00102080ff',
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.push_transaction(callParams);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls push_ro_transaction', async () => {
const expPath = '/v1/chain/push_ro_transaction';
const signatures = [
'George Washington',
'John Hancock',
'Abraham Lincoln',
];
const serializedTransaction = new Uint8Array([
0, 16, 32, 128, 255,
]);
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
transaction: {
signatures,
compression: 0,
packed_context_free_data: '',
packed_trx: '00102080ff'
},
return_failure_traces: false
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.push_ro_transaction({ signatures, serializedTransaction });
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls send_transaction', async () => {
const expPath = '/v1/chain/send_transaction';
const signatures = [
'George Washington',
'John Hancock',
'Abraham Lincoln',
];
const serializedTransaction = new Uint8Array([
0, 16, 32, 128, 255,
]);
const limit = 50;
const expReturn = { data: '12345' };
const callParams = {
signatures,
serializedTransaction,
};
const expParams = {
body: JSON.stringify({
signatures,
compression: 0,
packed_context_free_data: '',
packed_trx: '00102080ff',
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.send_transaction(callParams);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls db_size_get', async () => {
const expPath = '/v1/db_size/get';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.db_size_get();
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls history_get_actions with all params', async () => {
const expPath = '/v1/history/get_actions';
const accountName = 'myaccountaaa';
const pos = 5;
const offset = 10;
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
account_name: accountName,
pos,
offset,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.history_get_actions(accountName, pos, offset);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls history_get_actions with default params', async () => {
const expPath = '/v1/history/get_actions';
const accountName = 'myaccountaaa';
const pos: number = null;
const offset: number = null;
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
account_name: accountName,
pos,
offset,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.history_get_actions(accountName);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls history_get_transaction with all params', async () => {
const expPath = '/v1/history/get_transaction';
const id = 'myaccountaaa';
const blockNumHint = 20;
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
id,
block_num_hint: blockNumHint,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.history_get_transaction(id, blockNumHint);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls history_get_transaction with default params', async () => {
const expPath = '/v1/history/get_transaction';
const id = 'myaccountaaa';
const blockNumHint: number = null;
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
id,
block_num_hint: blockNumHint,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.history_get_transaction(id);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls history_get_key_accounts', async () => {
const expPath = '/v1/history/get_key_accounts';
const publicKey = 'key12345';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
public_key: publicKey,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.history_get_key_accounts(publicKey);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
it('calls history_get_controlled_accounts', async () => {
const expPath = '/v1/history/get_controlled_accounts';
const controllingAccount = 'key12345';
const expReturn = { data: '12345' };
const expParams = {
body: JSON.stringify({
controlling_account: controllingAccount,
}),
method: 'POST',
};
fetchMock.once(JSON.stringify(expReturn));
const response = await jsonRpc.history_get_controlled_accounts(controllingAccount);
expect(response).toEqual(expReturn);
expect(fetch).toBeCalledWith(endpoint + expPath, expParams);
});
});
import {ec} from 'elliptic';
import {generateKeyPair, PrivateKey, PublicKey, sha256, Signature} from '../zswjs-key-conversions';
import {digestFromSerializedData, JsSignatureProvider} from '../zswjs-jssig';
import {KeyType} from '../zswjs-numeric';
import {SignatureProviderArgs} from '../zswjs-api-interfaces';
describe('JsSignatureProvider', () => {
const privateKeys = [
'5Juww5SS6aLWxopXBAWzwqrwadiZKz7XpKAiktXTKcfBGi1DWg8',
'5JnHjSFwe4r7xyqAUAaVs51G7HmzE86DWGa3VAA5VvQriGYnSUr',
'5K4XZH5XR2By7Q5KTcZnPAmUMU5yjUNBdoKzzXyrLfmiEZJqoKE',
];
const privateKeysK1 = [
'PVT_K1_26fMPzM27mXhoSF8y56ro7pN2te7rFT6W6wXiUi5joY79NHfZf',
'PVT_K1_y19korZcH8hyStRy8bn2G8tgx51zE8nTWGFz7LG3ZDYkaELTY',
'PVT_K1_2FEybdSLZcyrPh3RR7tJ82M8sG4XLW6uzGDmMw76nv54xk8FLu',
];
const privateKeysR1 = [
'PVT_R1_GrfEfbv5at9kbeHcGagQmvbFLdm6jqEpgE1wsGbrfbZNjpVgT',
'PVT_R1_wCpPsaY9o8NU9ZsuwaYVQUDkCfj1aWJZGVcmMM6XyYHJVqvqp',
];
const legacyPublicKeys = [
'ZSW7tgwU6E7pAUQJgqEJt66Yi8cWvanTUW8ZfBjeXeJBQvhTU9ypi',
'ZSW8VaY5CiTexYqgQZyPTJkc3qvWuZUi12QrZL9ssjqW2es6aQk2F',
'ZSW7VGhqctkKprW1VUj19DZZiiZLX3YcJqUJCuEcahJmUCw3wJEMu',
];
const k1FormatPublicKeys = [
'PUB_K1_7tgwU6E7pAUQJgqEJt66Yi8cWvanTUW8ZfBjeXeJBQvhYTBFvY',
'PUB_K1_8VaY5CiTexYqgQZyPTJkc3qvWuZUi12QrZL9ssjqW2es7e7bRJ',
'PUB_K1_7VGhqctkKprW1VUj19DZZiiZLX3YcJqUJCuEcahJmUCw9RT8v2',
];
const r1FormatPublicKeys = [
'PUB_R1_4ztaVy8L9zbmzTdpfq5GcaFYwGwXTNmN3qW7qcgHMmfUZhpzQQ',
'PUB_R1_5xawnnr3mWayv2wkiqBGWqu4RQLNJffLSXHiL3BofdY7ortMy4',
];
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
describe('secp256k1 elliptic', () => {
it('generates a private and public key pair', () => {
const {privateKey, publicKey} = generateKeyPair(KeyType.k1, { secureEnv: true });
expect(privateKey).toBeInstanceOf(PrivateKey);
expect(privateKey.isValid()).toBeTruthy();
expect(publicKey).toBeInstanceOf(PublicKey);
expect(publicKey.isValid()).toBeTruthy();
});
it('throws error with no options.secureEnv variable', () => {
expect(() => generateKeyPair(KeyType.k1)).toThrowError();
});
it('Retrieves the public key from a private key', () => {
const privateKey = PrivateKey.fromString(privateKeys[0]);
const publicKey = privateKey.getPublicKey();
expect(publicKey.toString()).toEqual(k1FormatPublicKeys[0]);
});
it('builds public keys from private when constructed', async () => {
const provider = new JsSignatureProvider(privateKeys);
const actualPublicKeys = await provider.getAvailableKeys();
expect(actualPublicKeys).toEqual(k1FormatPublicKeys);
});
it('signs a transaction', async () => {
const provider = new JsSignatureProvider(privateKeys);
const chainId = '12345';
const requiredKeys = k1FormatPublicKeys;
const serializedTransaction = new Uint8Array([
0, 16, 32, 128, 255,
]);
const signOutput = await provider.sign(
{ chainId, requiredKeys, serializedTransaction } as SignatureProviderArgs
);
expect(signOutput).toEqual({
signatures: expect.any(Array),
serializedTransaction,
serializedContextFreeData: undefined
});
});
it('confirm elliptic conversion functions are actually reciprocal', async () => {
const provider = new JsSignatureProvider(privateKeys);
const chainId = '12345';
const requiredKeys = k1FormatPublicKeys;
const serializedTransaction = new Uint8Array([
0, 16, 32, 128, 255,
]);
const signOutput = await provider.sign(
{ chainId, requiredKeys, serializedTransaction } as SignatureProviderArgs
);
const sig: Signature = Signature.fromString(signOutput.signatures[0]);
const ellipticSig: ec.Signature = sig.toElliptic();
const zswSig = Signature.fromElliptic(ellipticSig, KeyType.k1);
expect(sig).toEqual(zswSig);
});
it('verify a transaction', async () => {
const provider = new JsSignatureProvider([privateKeys[0]]);
const chainId = '12345';
const requiredKeys = [k1FormatPublicKeys[0]];
const serializedTransaction = new Uint8Array([
0, 16, 32, 128, 255,
]);
const signOutput = await provider.sign(
{ chainId, requiredKeys, serializedTransaction } as SignatureProviderArgs
);
const signature = Signature.fromString(signOutput.signatures[0]);
expect(
signature.verify(
digestFromSerializedData(chainId, serializedTransaction),
PublicKey.fromString(k1FormatPublicKeys[0]),
false
)
).toEqual(true);
});
it('ensure public key functions are actual inverses of each other', async () => {
const zswchainPubKey = PublicKey.fromString(k1FormatPublicKeys[0]);
const ellipticPubKey = zswchainPubKey.toElliptic();
const finalZSWChainKeyAsK1String = PublicKey.fromElliptic(ellipticPubKey, KeyType.k1).toString();
expect(finalZSWChainKeyAsK1String).toEqual(k1FormatPublicKeys[0]);
});
it('verify that PUB_K1_ and Legacy pub formats are consistent', () => {
const zswchainLegacyPubKey = legacyPublicKeys[0];
const ellipticPubKey = PublicKey.fromString(zswchainLegacyPubKey).toElliptic();
expect(PublicKey.fromElliptic(ellipticPubKey, KeyType.k1).toString()).toEqual(k1FormatPublicKeys[0]);
});
it('verify that privateKey toLegacyString() and toString() are consistent', () => {
const privKeyFromK1 = PrivateKey.fromString(privateKeysK1[0]);
const privKeyFromLegacy = PrivateKey.fromString(privateKeys[0]);
expect(privKeyFromK1.toLegacyString()).toEqual(privateKeys[0]);
expect(privKeyFromLegacy.toString()).toEqual(privateKeysK1[0]);
});
it('verify that publicKey toLegacyString() and toString() are consistent', () => {
const pubKeyFromK1 = PublicKey.fromString(k1FormatPublicKeys[0]);
const pubKeyFromLegacy = PublicKey.fromString(legacyPublicKeys[0]);
expect(pubKeyFromK1.toLegacyString()).toEqual(legacyPublicKeys[0]);
expect(pubKeyFromLegacy.toString()).toEqual(k1FormatPublicKeys[0]);
});
it('ensure private key functions are actual inverses of each other', async () => {
const priv = privateKeys[0];
const privZSWChainKey = PrivateKey.fromString(priv);
const privEllipticKey = privZSWChainKey.toElliptic();
const finalZSWChainKeyAsString = PrivateKey.fromElliptic(privEllipticKey, KeyType.k1).toString();
expect(privZSWChainKey.toString()).toEqual(finalZSWChainKeyAsString);
});
it('verify that public key validate function correctly assesses public keys', () => {
const publicKey = PublicKey.fromString(k1FormatPublicKeys[0]);
expect(publicKey.isValid()).toEqual(true);
});
it('Ensure elliptic sign, recover, verify flow works', () => {
const KPrivStr = privateKeys[0];
const KPriv = PrivateKey.fromString(KPrivStr);
const dataAsString = 'some string';
const ellipticHashedString = sha256(dataAsString);
const sig = KPriv.sign(ellipticHashedString);
const KPub = sig.recover(ellipticHashedString);
expect(KPub.toString()).toEqual(k1FormatPublicKeys[0]);
const valid = sig.verify(ellipticHashedString, KPub);
expect(valid).toEqual(true);
});
it('Ensure elliptic sign, recover, verify flow works with shouldHash', () => {
const KPrivStr = privateKeys[0];
const KPriv = PrivateKey.fromString(KPrivStr);
const dataAsString = 'some string';
const sig = KPriv.sign(dataAsString, true);
const KPub = sig.recover(dataAsString, true);
expect(KPub.toString()).toEqual(k1FormatPublicKeys[0]);
const valid = sig.verify(dataAsString, KPub, true);
expect(valid).toEqual(true);
});
it('Ensure elliptic sign, recover, verify flow works with shouldHash and encoding', () => {
const KPrivStr = privateKeys[0];
const KPriv = PrivateKey.fromString(KPrivStr);
const dataAsString = 'some string';
const sig = KPriv.sign(dataAsString, true, 'utf8');
const KPub = sig.recover(dataAsString, true, 'utf8');
expect(KPub.toString()).toEqual(k1FormatPublicKeys[0]);
const valid = sig.verify(dataAsString, KPub, true, 'utf8');
expect(valid).toEqual(true);
});
});
describe('p256 elliptic', () => {
it('generates a private and public key pair', () => {
const {privateKey, publicKey} = generateKeyPair(KeyType.r1, { secureEnv: true });
expect(privateKey).toBeInstanceOf(PrivateKey);
expect(privateKey.isValid()).toBeTruthy();
expect(publicKey).toBeInstanceOf(PublicKey);
expect(publicKey.isValid()).toBeTruthy();
});
it('throws error with no options.secureEnv variable', () => {
expect(() => generateKeyPair(KeyType.r1)).toThrowError();
});
it('throws error when attempting a legacy private key from r1 format', () => {
const privateKey = PrivateKey.fromString(privateKeysR1[0]);
expect(() => privateKey.toLegacyString()).toThrowError('Key format not supported in legacy conversion');
});
it('throws error when attempting a legacy public key from r1 format', () => {
const publicKey = PublicKey.fromString(r1FormatPublicKeys[0]);
expect(() => publicKey.toLegacyString()).toThrowError('Key format not supported in legacy conversion');
});
it('Retrieves the public key from a private key', () => {
const privateKey = PrivateKey.fromString(privateKeysR1[0]);
const publicKey = privateKey.getPublicKey();
expect(publicKey.toString()).toEqual(r1FormatPublicKeys[0]);
});
it('builds public keys from private when constructed', async () => {
const provider = new JsSignatureProvider(privateKeysR1);
const actualPublicKeys = await provider.getAvailableKeys();
expect(actualPublicKeys).toEqual(r1FormatPublicKeys);
});
it('signs a transaction', async () => {
const provider = new JsSignatureProvider(privateKeysR1);
const chainId = '12345';
const requiredKeys = r1FormatPublicKeys;
const serializedTransaction = new Uint8Array([
0, 16, 32, 128, 255,
]);
const signOutput = await provider.sign(
{ chainId, requiredKeys, serializedTransaction } as SignatureProviderArgs
);
expect(signOutput).toEqual({
signatures: expect.any(Array),
serializedTransaction,
serializedContextFreeData: undefined
});
});
it('confirm elliptic conversion functions are actually reciprocal', async () => {
const provider = new JsSignatureProvider(privateKeysR1);
const chainId = '12345';
const requiredKeys = r1FormatPublicKeys;
const serializedTransaction = new Uint8Array([
0, 16, 32, 128, 255,
]);
const signOutput = await provider.sign(
{ chainId, requiredKeys, serializedTransaction } as SignatureProviderArgs
);
const sig: Signature = Signature.fromString(signOutput.signatures[0]);
const ellipticSig: ec.Signature = sig.toElliptic();
const zswSig = Signature.fromElliptic(ellipticSig, KeyType.r1);
expect(sig).toEqual(zswSig);
});
it('verify a transaction', async () => {
const provider = new JsSignatureProvider([privateKeysR1[0]]);
const chainId = '12345';
const requiredKeys = [r1FormatPublicKeys[0]];
const serializedTransaction = new Uint8Array([
0, 16, 32, 128, 255,
]);
const signOutput = await provider.sign(
{ chainId, requiredKeys, serializedTransaction } as SignatureProviderArgs
);
const signature = Signature.fromString(signOutput.signatures[0]);
expect(
signature.verify(
digestFromSerializedData(chainId, serializedTransaction),
PublicKey.fromString(r1FormatPublicKeys[0]),
false
)
).toEqual(true);
});
it('ensure public key functions using p256 format are actual inverses of each other', async () => {
const zswchainPubKey = PublicKey.fromString(r1FormatPublicKeys[0]);
const ellipticPubKey = zswchainPubKey.toElliptic();
const finalZSWChainKeyAsR1String = PublicKey.fromElliptic(ellipticPubKey, KeyType.r1).toString();
expect(finalZSWChainKeyAsR1String).toEqual(r1FormatPublicKeys[0]);
});
it('ensure private key functions using p256 format are actual inverses of each other', async () => {
const priv = privateKeysR1[0];
const privZSWChainKey = PrivateKey.fromString(priv);
const privEllipticKey = privZSWChainKey.toElliptic();
const finalZSWChainKeyAsString = PrivateKey.fromElliptic(privEllipticKey, KeyType.r1).toString();
expect(privZSWChainKey.toString()).toEqual(finalZSWChainKeyAsString);
});
it('verify that public key validate function correctly assesses public keys', () => {
const publicKey = PublicKey.fromString(r1FormatPublicKeys[0]);
expect(publicKey.isValid()).toEqual(true);
});
it('Ensure elliptic sign, recover, verify flow works', () => {
const KPrivStr = privateKeysR1[0];
const KPriv = PrivateKey.fromString(KPrivStr);
const dataAsString = 'some string';
const ellipticHashedString = sha256(dataAsString);
const sig = KPriv.sign(ellipticHashedString);
const KPub = sig.recover(ellipticHashedString);
expect(KPub.toString()).toEqual(r1FormatPublicKeys[0]);
const valid = sig.verify(ellipticHashedString, KPub);
expect(valid).toEqual(true);
});
it('Ensure elliptic sign, recover, verify flow works with shouldHash', () => {
const KPrivStr = privateKeysR1[0];
const KPriv = PrivateKey.fromString(KPrivStr);
const dataAsString = 'some string';
const sig = KPriv.sign(dataAsString, true);
const KPub = sig.recover(dataAsString, true);
expect(KPub.toString()).toEqual(r1FormatPublicKeys[0]);
const valid = sig.verify(dataAsString, KPub, true);
expect(valid).toEqual(true);
});
it('Ensure elliptic sign, recover, verify flow works with shouldHash and encoding', () => {
const KPrivStr = privateKeysR1[0];
const KPriv = PrivateKey.fromString(KPrivStr);
const dataAsString = 'some string';
const sig = KPriv.sign(dataAsString, true, 'utf8');
const KPub = sig.recover(dataAsString, true, 'utf8');
expect(KPub.toString()).toEqual(r1FormatPublicKeys[0]);
const valid = sig.verify(dataAsString, KPub, true, 'utf8');
expect(valid).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 API
*/
// copyright defined in zswjs/LICENSE.txt
/* eslint-disable max-classes-per-file */
import { inflate, deflate } from 'pako';
import {
AbiProvider,
ActionSerializerType,
AuthorityProvider,
BinaryAbi,
CachedAbi,
ContextFreeGroupCallback,
Query,
QueryConfig,
SignatureProvider,
TransactConfig,
Transaction,
TransactResult,
} from './zswjs-api-interfaces';
import { JsonRpc } from './zswjs-jsonrpc';
import {
Abi,
BlockTaposInfo,
GetInfoResult,
PushTransactionArgs,
GetBlockHeaderStateResult,
GetBlockInfoResult,
GetBlockResult,
ReadOnlyTransactResult,
} from './zswjs-rpc-interfaces';
import * as ser from './zswjs-serialize';
export class Api {
/** Issues RPC calls */
public rpc: JsonRpc;
/** Get subset of `availableKeys` needed to meet authorities in a `transaction` */
public authorityProvider: AuthorityProvider;
/** Supplies ABIs in raw form (binary) */
public abiProvider: AbiProvider;
/** Signs transactions */
public signatureProvider: SignatureProvider;
/** Identifies chain */
public chainId: string;
public textEncoder: TextEncoder;
public textDecoder: TextDecoder;
/** Converts abi files between binary and structured form (`abi.abi.json`) */
public abiTypes: Map<string, ser.Type>;
/** Converts transactions between binary and structured form (`transaction.abi.json`) */
public transactionTypes: Map<string, ser.Type>;
/** Holds information needed to serialize contract actions */
public contracts = new Map<string, ser.Contract>();
/** Fetched abis */
public cachedAbis = new Map<string, CachedAbi>();
/**
* @param args
* * `rpc`: Issues RPC calls
* * `authorityProvider`: Get public keys needed to meet authorities in a transaction
* * `abiProvider`: Supplies ABIs in raw form (binary)
* * `signatureProvider`: Signs transactions
* * `chainId`: Identifies chain
* * `textEncoder`: `TextEncoder` instance to use. Pass in `null` if running in a browser
* * `textDecoder`: `TextDecoder` instance to use. Pass in `null` if running in a browser
*/
constructor(args: {
rpc: JsonRpc,
authorityProvider?: AuthorityProvider,
abiProvider?: AbiProvider,
signatureProvider: SignatureProvider,
chainId?: string,
textEncoder?: TextEncoder,
textDecoder?: TextDecoder,
}) {
this.rpc = args.rpc;
this.authorityProvider = args.authorityProvider || args.rpc;
this.abiProvider = args.abiProvider || args.rpc;
this.signatureProvider = args.signatureProvider;
this.chainId = args.chainId;
this.textEncoder = args.textEncoder;
this.textDecoder = args.textDecoder;
this.abiTypes = ser.getTypesFromAbi(ser.createAbiTypes());
this.transactionTypes = ser.getTypesFromAbi(ser.createTransactionTypes());
}
/** Decodes an abi as Uint8Array into json. */
public rawAbiToJson(rawAbi: Uint8Array): Abi {
const buffer = new ser.SerialBuffer({
textEncoder: this.textEncoder,
textDecoder: this.textDecoder,
array: rawAbi,
});
if (!ser.supportedAbiVersion(buffer.getString())) {
throw new Error('Unsupported abi version');
}
buffer.restartRead();
return this.abiTypes.get('abi_def').deserialize(buffer);
}
/** Encodes a json abi as Uint8Array. */
public jsonToRawAbi(jsonAbi: Abi): Uint8Array {
const buffer = new ser.SerialBuffer({
textEncoder: this.textEncoder,
textDecoder: this.textDecoder,
});
this.abiTypes.get('abi_def').serialize(buffer, jsonAbi);
if (!ser.supportedAbiVersion(buffer.getString())) {
throw new Error('Unsupported abi version');
}
return buffer.asUint8Array();
}
/** Get abi in both binary and structured forms. Fetch when needed. */
public async getCachedAbi(accountName: string, reload = false): Promise<CachedAbi> {
if (!reload && this.cachedAbis.get(accountName)) {
return this.cachedAbis.get(accountName);
}
let cachedAbi: CachedAbi;
try {
const rawAbi = (await this.abiProvider.getRawAbi(accountName)).abi;
const abi = this.rawAbiToJson(rawAbi);
cachedAbi = { rawAbi, abi };
} catch (e) {
e.message = `fetching abi for ${accountName}: ${e.message}`;
throw e;
}
if (!cachedAbi) {
throw new Error(`Missing abi for ${accountName}`);
}
this.cachedAbis.set(accountName, cachedAbi);
return cachedAbi;
}
/** Get abi in structured form. Fetch when needed. */
public async getAbi(accountName: string, reload = false): Promise<Abi> {
return (await this.getCachedAbi(accountName, reload)).abi;
}
/** Get abis needed by a transaction */
public async getTransactionAbis(transaction: Transaction, reload = false): Promise<BinaryAbi[]> {
const actions = (transaction.context_free_actions || []).concat(transaction.actions);
const accounts: string[] = actions.map((action: ser.Action): string => action.account);
const uniqueAccounts: Set<string> = new Set(accounts);
const actionPromises: Promise<BinaryAbi>[] = [...uniqueAccounts].map(
async (account: string): Promise<BinaryAbi> => ({
accountName: account, abi: (await this.getCachedAbi(account, reload)).rawAbi,
}));
return Promise.all(actionPromises);
}
/** Get data needed to serialize actions in a contract */
public async getContract(accountName: string, reload = false): Promise<ser.Contract> {
if (!reload && this.contracts.get(accountName)) {
return this.contracts.get(accountName);
}
const abi = await this.getAbi(accountName, reload);
const types = ser.getTypesFromAbi(ser.createInitialTypes(), abi);
const actions = new Map<string, ser.Type>();
for (const { name, type } of abi.actions) {
actions.set(name, ser.getType(types, type));
}
const result = { types, actions };
this.contracts.set(accountName, result);
return result;
}
/** Convert `value` to binary form. `type` must be a built-in abi type or in `transaction.abi.json`. */
public serialize(buffer: ser.SerialBuffer, type: string, value: any): void {
this.transactionTypes.get(type).serialize(buffer, value);
}
/** Convert data in `buffer` to structured form. `type` must be a built-in abi type or in `transaction.abi.json`. */
public deserialize(buffer: ser.SerialBuffer, type: string): any {
return this.transactionTypes.get(type).deserialize(buffer);
}
/** Convert a transaction to binary */
public serializeTransaction(transaction: Transaction): Uint8Array {
const buffer = new ser.SerialBuffer({ textEncoder: this.textEncoder, textDecoder: this.textDecoder });
this.serialize(buffer, 'transaction', {
max_net_usage_words: 0,
max_cpu_usage_ms: 0,
delay_sec: 0,
context_free_actions: [],
actions: [],
transaction_extensions: [],
...transaction,
});
return buffer.asUint8Array();
}
/** Serialize context-free data */
public serializeContextFreeData(contextFreeData: Uint8Array[]): Uint8Array {
if (!contextFreeData || !contextFreeData.length) {
return null;
}
const buffer = new ser.SerialBuffer({ textEncoder: this.textEncoder, textDecoder: this.textDecoder });
buffer.pushVaruint32(contextFreeData.length);
for (const data of contextFreeData) {
buffer.pushBytes(data);
}
return buffer.asUint8Array();
}
/** Convert a transaction from binary. Leaves actions in hex. */
public deserializeTransaction(transaction: Uint8Array): Transaction {
const buffer = new ser.SerialBuffer({ textEncoder: this.textEncoder, textDecoder: this.textDecoder });
buffer.pushArray(transaction);
return this.deserialize(buffer, 'transaction');
}
private transactionExtensions = [
{ id: 1, type: 'resource_payer', keys: ['payer', 'max_net_bytes', 'max_cpu_us', 'max_memory_bytes'] },
];
// Order of adding to transaction_extension is transaction_extension id ascending
public serializeTransactionExtensions(transaction: Transaction): [number, string][] {
let transaction_extensions: [number, string][] = [];
if (transaction.resource_payer) {
const extensionBuffer = new ser.SerialBuffer({ textEncoder: this.textEncoder, textDecoder: this.textDecoder });
const types = ser.getTypesFromAbi(ser.createTransactionExtensionTypes());
types.get('resource_payer').serialize(extensionBuffer, transaction.resource_payer);
transaction_extensions = [...transaction_extensions, [1, ser.arrayToHex(extensionBuffer.asUint8Array())]];
}
return transaction_extensions;
};
// Usage: transaction = {...transaction, ...this.deserializeTransactionExtensions(transaction.transaction_extensions)}
public deserializeTransactionExtensions(data: [number, string][]): any[] {
const transaction = {} as any;
data.forEach((extensionData: [number, string]) => {
const transactionExtension = this.transactionExtensions.find(extension => extension.id === extensionData[0]);
if (transactionExtension === undefined) {
throw new Error(`Transaction Extension could not be determined: ${extensionData}`);
}
const types = ser.getTypesFromAbi(ser.createTransactionExtensionTypes());
const extensionBuffer = new ser.SerialBuffer({ textEncoder: this.textEncoder, textDecoder: this.textDecoder });
extensionBuffer.pushArray(ser.hexToUint8Array(extensionData[1]));
const deserializedObj = types.get(transactionExtension.type).deserialize(extensionBuffer);
if (extensionData[0] === 1) {
deserializedObj.max_net_bytes = Number(deserializedObj.max_net_bytes);
deserializedObj.max_cpu_us = Number(deserializedObj.max_cpu_us);
deserializedObj.max_memory_bytes = Number(deserializedObj.max_memory_bytes);
transaction.resource_payer = deserializedObj;
}
});
return transaction;
};
// Transaction extensions are serialized and moved to `transaction_extensions`, deserialized objects are not needed on the transaction
public deleteTransactionExtensionObjects(transaction: Transaction): Transaction {
delete transaction.resource_payer;
return transaction;
}
/** Convert actions to hex */
public async serializeActions(actions: ser.Action[]): Promise<ser.SerializedAction[]> {
return await Promise.all(actions.map(async (action) => {
const { account, name, authorization, data } = action;
const contract = await this.getContract(account);
if (typeof data !== 'object') {
return action;
}
return ser.serializeAction(
contract, account, name, authorization, data, this.textEncoder, this.textDecoder);
}));
}
/** Convert actions from hex */
public async deserializeActions(actions: ser.Action[]): Promise<ser.Action[]> {
return await Promise.all(actions.map(async ({ account, name, authorization, data }) => {
const contract = await this.getContract(account);
return ser.deserializeAction(
contract, account, name, authorization, data, this.textEncoder, this.textDecoder);
}));
}
/** Convert a transaction from binary. Also deserializes actions. */
public async deserializeTransactionWithActions(transaction: Uint8Array | string): Promise<Transaction> {
if (typeof transaction === 'string') {
transaction = ser.hexToUint8Array(transaction);
}
const deserializedTransaction = this.deserializeTransaction(transaction);
const deserializedCFActions = await this.deserializeActions(deserializedTransaction.context_free_actions);
const deserializedActions = await this.deserializeActions(deserializedTransaction.actions);
return {
...deserializedTransaction, context_free_actions: deserializedCFActions, actions: deserializedActions
};
}
/** Deflate a serialized object */
public deflateSerializedArray(serializedArray: Uint8Array): Uint8Array {
return deflate(serializedArray, { level: 9 });
}
/** Inflate a compressed serialized object */
public inflateSerializedArray(compressedSerializedArray: Uint8Array): Uint8Array {
return inflate(compressedSerializedArray);
}
/**
* Create and optionally broadcast a transaction.
*
* Named Parameters:
* `broadcast`: broadcast this transaction?
* `sign`: sign this transaction?
* `compression`: compress this transaction?
* `readOnlyTrx`: read only transaction?
* `returnFailureTraces`: return failure traces? (only available for read only transactions currently)
*
* If both `blocksBehind` and `expireSeconds` are present,
* then fetch the block which is `blocksBehind` behind head block,
* use it as a reference for TAPoS, and expire the transaction `expireSeconds` after that block's time.
*
* If both `useLastIrreversible` and `expireSeconds` are present,
* then fetch the last irreversible block, use it as a reference for TAPoS,
* and expire the transaction `expireSeconds` after that block's time.
*
* @returns node response if `broadcast`, `{signatures, serializedTransaction}` if `!broadcast`
*/
public async transact(
transaction: Transaction,
{
broadcast = true,
sign = true,
readOnlyTrx,
returnFailureTraces,
requiredKeys,
compression,
blocksBehind,
useLastIrreversible,
expireSeconds
}:
TransactConfig = {}): Promise<TransactResult|ReadOnlyTransactResult|PushTransactionArgs>
{
let info: GetInfoResult;
if (typeof blocksBehind === 'number' && useLastIrreversible) {
throw new Error('Use either blocksBehind or useLastIrreversible');
}
if (!this.chainId) {
info = await this.rpc.get_info();
this.chainId = info.chain_id;
}
if ((typeof blocksBehind === 'number' || useLastIrreversible) && expireSeconds) {
transaction = await this.generateTapos(info, transaction, blocksBehind, useLastIrreversible, expireSeconds);
}
if (!this.hasRequiredTaposFields(transaction)) {
throw new Error('Required configuration or TAPOS fields are not present');
}
const abis: BinaryAbi[] = await this.getTransactionAbis(transaction);
transaction = {
...transaction,
transaction_extensions: await this.serializeTransactionExtensions(transaction),
context_free_actions: await this.serializeActions(transaction.context_free_actions || []),
actions: await this.serializeActions(transaction.actions)
};
transaction = this.deleteTransactionExtensionObjects(transaction);
const serializedTransaction = this.serializeTransaction(transaction);
const serializedContextFreeData = this.serializeContextFreeData(transaction.context_free_data);
let pushTransactionArgs: PushTransactionArgs = {
serializedTransaction, serializedContextFreeData, signatures: []
};
if (sign) {
if (!requiredKeys) {
const availableKeys = await this.signatureProvider.getAvailableKeys();
requiredKeys = await this.authorityProvider.getRequiredKeys({ transaction, availableKeys });
}
pushTransactionArgs = await this.signatureProvider.sign({
chainId: this.chainId,
requiredKeys,
serializedTransaction,
serializedContextFreeData,
abis,
});
}
if (broadcast) {
if (compression) {
return this.pushCompressedSignedTransaction(
pushTransactionArgs,
readOnlyTrx,
returnFailureTraces,
) as Promise<TransactResult|ReadOnlyTransactResult>;
}
return this.pushSignedTransaction(
pushTransactionArgs,
readOnlyTrx,
returnFailureTraces,
) as Promise<TransactResult|ReadOnlyTransactResult>;
}
return pushTransactionArgs as PushTransactionArgs;
}
public async query(
account: string, short: boolean, query: Query,
{ sign, requiredKeys, authorization = [] }: QueryConfig
): Promise<any> {
const info = await this.rpc.get_info();
const refBlock = await this.tryRefBlockFromGetInfo(info);
const queryBuffer = new ser.SerialBuffer({ textEncoder: this.textEncoder, textDecoder: this.textDecoder });
ser.serializeQuery(queryBuffer, query);
const transaction = {
...ser.transactionHeader(refBlock, 60 * 30),
context_free_actions: [] as ser.Action[],
actions: [{
account,
name: 'queryit',
authorization,
data: ser.arrayToHex(queryBuffer.asUint8Array()),
}],
};
const serializedTransaction = this.serializeTransaction(transaction);
let signatures: string[] = [];
if (sign) {
const abis: BinaryAbi[] = await this.getTransactionAbis(transaction);
if (!requiredKeys) {
const availableKeys = await this.signatureProvider.getAvailableKeys();
requiredKeys = await this.authorityProvider.getRequiredKeys({ transaction, availableKeys });
}
const signResponse = await this.signatureProvider.sign({
chainId: this.chainId,
requiredKeys,
serializedTransaction,
serializedContextFreeData: null,
abis,
});
signatures = signResponse.signatures;
}
const response = await this.rpc.send_transaction({
signatures,
compression: 0,
serializedTransaction
}) as any;
const returnBuffer = new ser.SerialBuffer({
textEncoder: this.textEncoder,
textDecoder: this.textDecoder,
array: ser.hexToUint8Array(response.processed.action_traces[0][1].return_value)
});
if (short) {
return ser.deserializeAnyvarShort(returnBuffer);
} else {
return ser.deserializeAnyvar(returnBuffer);
}
}
/** Broadcast a signed transaction */
public async pushSignedTransaction(
{ signatures, serializedTransaction, serializedContextFreeData }: PushTransactionArgs,
readOnlyTrx = false,
returnFailureTraces = false,
): Promise<TransactResult|ReadOnlyTransactResult> {
if (readOnlyTrx) {
return this.rpc.push_ro_transaction({
signatures,
serializedTransaction,
serializedContextFreeData,
}, returnFailureTraces);
}
return this.rpc.push_transaction({
signatures,
serializedTransaction,
serializedContextFreeData
});
}
public async pushCompressedSignedTransaction(
{ signatures, serializedTransaction, serializedContextFreeData }: PushTransactionArgs,
readOnlyTrx = false,
returnFailureTraces = false,
): Promise<TransactResult|ReadOnlyTransactResult> {
const compressedSerializedTransaction = this.deflateSerializedArray(serializedTransaction);
const compressedSerializedContextFreeData =
this.deflateSerializedArray(serializedContextFreeData || new Uint8Array(0));
if (readOnlyTrx) {
return this.rpc.push_ro_transaction({
signatures,
compression: 1,
serializedTransaction: compressedSerializedTransaction,
serializedContextFreeData: compressedSerializedContextFreeData
}, returnFailureTraces);
}
return this.rpc.push_transaction({
signatures,
compression: 1,
serializedTransaction: compressedSerializedTransaction,
serializedContextFreeData: compressedSerializedContextFreeData
});
}
private async generateTapos(
info: GetInfoResult | undefined,
transaction: Transaction,
blocksBehind: number | undefined,
useLastIrreversible: boolean | undefined,
expireSeconds: number
): Promise<Transaction> {
if (!info) {
info = await this.rpc.get_info();
}
if (useLastIrreversible) {
const block = await this.tryRefBlockFromGetInfo(info);
return { ...ser.transactionHeader(block, expireSeconds), ...transaction };
}
const taposBlockNumber: number = info.head_block_num - blocksBehind;
const refBlock: GetBlockHeaderStateResult | GetBlockResult | GetBlockInfoResult =
taposBlockNumber <= info.last_irreversible_block_num
? await this.tryGetBlockInfo(taposBlockNumber)
: await this.tryGetBlockHeaderState(taposBlockNumber);
return { ...ser.transactionHeader(refBlock, expireSeconds), ...transaction };
}
// eventually break out into TransactionValidator class
private hasRequiredTaposFields({ expiration, ref_block_num, ref_block_prefix }: Transaction): boolean {
return !!(expiration && typeof(ref_block_num) === 'number' && typeof(ref_block_prefix) === 'number');
}
private async tryGetBlockHeaderState(taposBlockNumber: number): Promise<GetBlockHeaderStateResult | GetBlockResult | GetBlockInfoResult>
{
try {
return await this.rpc.get_block_header_state(taposBlockNumber);
} catch (error) {
return await this.tryGetBlockInfo(taposBlockNumber);
}
}
private async tryGetBlockInfo(blockNumber: number): Promise<GetBlockInfoResult | GetBlockResult> {
try {
return await this.rpc.get_block_info(blockNumber);
} catch (error) {
return await this.rpc.get_block(blockNumber);
}
}
private async tryRefBlockFromGetInfo(info: GetInfoResult): Promise<BlockTaposInfo | GetBlockInfoResult | GetBlockResult> {
if (
info.hasOwnProperty('last_irreversible_block_id') &&
info.hasOwnProperty('last_irreversible_block_num') &&
info.hasOwnProperty('last_irreversible_block_time')
) {
return {
block_num: info.last_irreversible_block_num,
id: info.last_irreversible_block_id,
timestamp: info.last_irreversible_block_time,
};
} else {
const block = await this.tryGetBlockInfo(info.last_irreversible_block_num);
return {
block_num: block.block_num,
id: block.id,
timestamp: block.timestamp,
};
}
}
public with(accountName: string): ActionBuilder {
return new ActionBuilder(this, accountName);
}
public buildTransaction(cb?: (tx: TransactionBuilder) => void): TransactionBuilder|void {
const tx = new TransactionBuilder(this);
if (cb) {
return cb(tx);
}
return tx as TransactionBuilder;
}
} // Api
export class TransactionBuilder {
private api: Api;
private actions: ActionBuilder[] = [];
private contextFreeGroups: ContextFreeGroupCallback[] = [];
constructor(api: Api) {
this.api = api;
}
public with(accountName: string): ActionBuilder {
const actionBuilder = new ActionBuilder(this.api, accountName);
this.actions.push(actionBuilder);
return actionBuilder;
}
public associateContextFree(contextFreeGroup: ContextFreeGroupCallback): TransactionBuilder {
this.contextFreeGroups.push(contextFreeGroup);
return this;
}
public async send(config?: TransactConfig): Promise<PushTransactionArgs|ReadOnlyTransactResult|TransactResult> {
const contextFreeDataSet: Uint8Array[] = [];
const contextFreeActions: ser.SerializedAction[] = [];
const actions: ser.SerializedAction[] = this.actions.map((actionBuilder) => actionBuilder.serializedData as ser.SerializedAction);
await Promise.all(this.contextFreeGroups.map(
async (contextFreeCallback: ContextFreeGroupCallback) => {
const { action, contextFreeAction, contextFreeData } = contextFreeCallback({
cfd: contextFreeDataSet.length,
cfa: contextFreeActions.length
});
if (action) {
actions.push(action);
}
if (contextFreeAction) {
contextFreeActions.push(contextFreeAction);
}
if (contextFreeData) {
contextFreeDataSet.push(contextFreeData);
}
}
));
this.contextFreeGroups = [];
this.actions = [];
return await this.api.transact({
context_free_data: contextFreeDataSet,
context_free_actions: contextFreeActions,
actions
}, config);
}
}
export class ActionBuilder {
private api: Api;
private readonly accountName: string;
public serializedData: ser.SerializedAction;
constructor(api: Api, accountName: string) {
this.api = api;
this.accountName = accountName;
}
public as(actorName: string | ser.Authorization[] = []): ActionSerializerType {
let authorization: ser.Authorization[] = [];
if (actorName && typeof actorName === 'string') {
authorization = [{ actor: actorName, permission: 'active'}];
} else {
authorization = actorName as ser.Authorization[];
}
return new ActionSerializer(this, this.api, this.accountName, authorization) as ActionSerializerType;
}
}
class ActionSerializer implements ActionSerializerType {
constructor(
parent: ActionBuilder,
api: Api,
accountName: string,
authorization: ser.Authorization[],
) {
const jsonAbi = api.cachedAbis.get(accountName);
if (!jsonAbi) {
throw new Error('ABI must be cached before using ActionBuilder, run api.getAbi()');
}
const types = ser.getTypesFromAbi(ser.createInitialTypes(), jsonAbi.abi);
const actions = new Map<string, ser.Type>();
for (const { name, type } of jsonAbi.abi.actions) {
actions.set(name, ser.getType(types, type));
}
actions.forEach((type, name) => {
Object.assign(this, {
[name]: (...args: any[]) => {
const data: { [key: string]: any } = {};
args.forEach((arg, index) => {
const field = type.fields[index];
data[field.name] = arg;
});
const serializedData = ser.serializeAction(
{ types, actions },
accountName,
name,
authorization,
data,
api.textEncoder,
api.textDecoder
);
parent.serializedData = serializedData;
return serializedData;
}
});
});
}
}
/**
* @module JSON-RPC
*/
// copyright defined in zswjs/LICENSE.txt
import { AbiProvider, AuthorityProvider, AuthorityProviderArgs, BinaryAbi, TransactResult } from './zswjs-api-interfaces';
import { base64ToBinary, convertLegacyPublicKeys } from './zswjs-numeric';
import {
AbiBinToJsonResult,
AbiJsonToBinResult,
GetAbiResult,
GetAccountResult,
GetAccountsByAuthorizersResult,
GetActivatedProtocolFeaturesParams,
GetActivatedProtocolFeaturesResult,
GetBlockInfoResult,
GetBlockResult,
GetCodeResult,
GetCodeHashResult,
GetCurrencyStatsResult,
GetInfoResult,
GetProducerScheduleResult,
GetProducersResult,
GetRawCodeAndAbiResult,
GetRawAbiResult,
GetScheduledTransactionsResult,
GetTableRowsResult,
PushTransactionArgs,
PackedTrx,
ReadOnlyTransactResult,
GetBlockHeaderStateResult,
GetTableByScopeResult,
DBSizeGetResult,
TraceApiGetBlockResult,
GetActionsResult,
GetTransactionResult,
GetKeyAccountsResult,
GetControlledAccountsResult,
} from './zswjs-rpc-interfaces';
import { Authorization } from './zswjs-serialize';
import { RpcError } from './zswjs-rpcerror';
const arrayToHex = (data: Uint8Array): string => {
let result = '';
for (const x of data) {
result += ('00' + x.toString(16)).slice(-2);
}
return result;
};
/** Make RPC calls */
export class JsonRpc implements AuthorityProvider, AbiProvider {
public endpoint: string;
public fetchBuiltin: (input?: any, init?: any) => Promise<any>;
/**
* @param args
* `fetch`:
* browsers: leave `null` or `undefined`
* node: provide an implementation
*/
constructor(
endpoint: string,
args: {
fetch?: (input?: any, init?: any) => Promise<any>
} = {}
) {
this.endpoint = endpoint.replace(/\/$/, '');
if (args.fetch) {
this.fetchBuiltin = args.fetch;
} else {
this.fetchBuiltin = (global as any).fetch;
}
}
/** Post `body` to `endpoint + path`. Throws detailed error information in `RpcError` when available. */
public async fetch(path: string, body: any): Promise<any> {
let response;
let json;
try {
const f = this.fetchBuiltin;
response = await f(this.endpoint + path, {
body: JSON.stringify(body),
method: 'POST',
});
json = await response.json();
if (json.processed && json.processed.except) {
throw new RpcError(json);
} else if (json.result && json.result.except) {
throw new RpcError(json);
}
} catch (e) {
e.isFetchError = true;
throw e;
}
if (!response.ok) {
throw new RpcError(json);
}
return json;
}
public async abi_bin_to_json(
code: string,
action: string,
binargs: string,
): Promise<AbiBinToJsonResult> {
return await this.fetch('/v1/chain/abi_bin_to_json', { code, action, binargs });
}
public async abi_json_to_bin(
code: string,
action: string,
args: any[],
): Promise<AbiJsonToBinResult> {
return await this.fetch('/v1/chain/abi_json_to_bin', { code, action, args });
}
/** Raw call to `/v1/chain/get_abi` */
public async get_abi(accountName: string): Promise<GetAbiResult> {
return await this.fetch('/v1/chain/get_abi', { account_name: accountName });
}
/** Raw call to `/v1/chain/get_account` */
public async get_account(accountName: string): Promise<GetAccountResult> {
return await this.fetch('/v1/chain/get_account', { account_name: accountName });
}
/** Raw call to `/v1/chain/get_accounts_by_authorizers` */
public async get_accounts_by_authorizers(accounts: Authorization[], keys: string[]): Promise<GetAccountsByAuthorizersResult> {
return await this.fetch('/v1/chain/get_accounts_by_authorizers', { accounts, keys });
}
/** Raw call to `get_activated_protocol_features` */
public async get_activated_protocol_features({
limit = 10,
search_by_block_num = false,
reverse = false,
lower_bound = null,
upper_bound = null,
}: GetActivatedProtocolFeaturesParams): Promise<GetActivatedProtocolFeaturesResult> {
return await this.fetch('/v1/chain/get_activated_protocol_features', { lower_bound, upper_bound, limit, search_by_block_num, reverse });
}
/** Raw call to `/v1/chain/get_block_header_state` */
public async get_block_header_state(blockNumOrId: number | string): Promise<GetBlockHeaderStateResult> {
return await this.fetch('/v1/chain/get_block_header_state', { block_num_or_id: blockNumOrId });
}
/** Raw call to `/v1/chain/get_block_info` */
public async get_block_info(blockNum: number): Promise<GetBlockInfoResult> {
return await this.fetch('/v1/chain/get_block_info', { block_num: blockNum });
}
/** Raw call to `/v1/chain/get_block` */
public async get_block(blockNumOrId: number | string): Promise<GetBlockResult> {
return await this.fetch('/v1/chain/get_block', { block_num_or_id: blockNumOrId });
}
/** Raw call to `/v1/chain/get_code` */
public async get_code(accountName: string): Promise<GetCodeResult> {
return await this.fetch('/v1/chain/get_code', {
account_name: accountName,
code_as_wasm: true
});
}
/** Raw call to `/v1/chain/get_code_hash` */
public async get_code_hash(accountName: string): Promise<GetCodeHashResult> {
return await this.fetch('/v1/chain/get_code_hash', { account_name: accountName });
}
/** Raw call to `/v1/chain/get_currency_balance` */
public async get_currency_balance(code: string, account: string, symbol: string = null): Promise<string[]> {
return await this.fetch('/v1/chain/get_currency_balance', { code, account, symbol });
}
/** Raw call to `/v1/chain/get_currency_stats` */
public async get_currency_stats(code: string, symbol: string): Promise<GetCurrencyStatsResult> {
return await this.fetch('/v1/chain/get_currency_stats', { code, symbol });
}
/** Raw call to `/v1/chain/get_info` */
public async get_info(): Promise<GetInfoResult> {
return await this.fetch('/v1/chain/get_info', {});
}
/** Raw call to `/v1/chain/get_producer_schedule` */
public async get_producer_schedule(): Promise<GetProducerScheduleResult> {
return await this.fetch('/v1/chain/get_producer_schedule', {});
}
/** Raw call to `/v1/chain/get_producers` */
public async get_producers(json = true, lowerBound = '', limit = 50): Promise<GetProducersResult> {
return await this.fetch('/v1/chain/get_producers', { json, lower_bound: lowerBound, limit });
}
/** Raw call to `/v1/chain/get_raw_code_and_abi` */
public async get_raw_code_and_abi(accountName: string): Promise<GetRawCodeAndAbiResult> {
return await this.fetch('/v1/chain/get_raw_code_and_abi', { account_name: accountName });
}
/** calls `/v1/chain/get_raw_code_and_abi` and pulls out unneeded raw wasm code */
public async getRawAbi(accountName: string): Promise<BinaryAbi> {
const rawAbi = await this.get_raw_abi(accountName);
const abi = base64ToBinary(rawAbi.abi);
return { accountName: rawAbi.account_name, abi };
}
/** Raw call to `/v1/chain/get_raw_abi` */
public async get_raw_abi(accountName: string): Promise<GetRawAbiResult> {
return await this.fetch('/v1/chain/get_raw_abi', { account_name: accountName });
}
/** Raw call to `/v1/chain/get_scheduled_transactions` */
public async get_scheduled_transactions(json = true, lowerBound = '', limit = 50): Promise<GetScheduledTransactionsResult> {
return await this.fetch('/v1/chain/get_scheduled_transactions', { json, lower_bound: lowerBound, limit });
}
/** Raw call to `/v1/chain/get_table_rows` */
public async get_table_rows({
json = true,
code,
scope,
table,
lower_bound = '',
upper_bound = '',
index_position = 1,
key_type = '',
limit = 10,
reverse = false,
show_payer = false,
}: any): Promise<GetTableRowsResult> {
return await this.fetch(
'/v1/chain/get_table_rows', {
json,
code,
scope,
table,
lower_bound,
upper_bound,
index_position,
key_type,
limit,
reverse,
show_payer,
});
}
/** Raw call to `/v1/chain/get_kv_table_rows` */
public async get_kv_table_rows({
json = true,
code,
table,
index_name,
encode_type = 'bytes',
index_value,
lower_bound,
upper_bound,
limit = 10,
reverse = false,
show_payer = false,
}: any): Promise<GetTableRowsResult> {
return await this.fetch(
'/v1/chain/get_kv_table_rows', {
json,
code,
table,
index_name,
encode_type,
index_value,
lower_bound,
upper_bound,
limit,
reverse,
show_payer,
});
}
/** Raw call to `/v1/chain/get_table_by_scope` */
public async get_table_by_scope({
code,
table,
lower_bound = '',
upper_bound = '',
limit = 10,
}: any): Promise<GetTableByScopeResult> {
return await this.fetch(
'/v1/chain/get_table_by_scope', {
code,
table,
lower_bound,
upper_bound,
limit,
});
}
/** Get subset of `availableKeys` needed to meet authorities in `transaction`. Implements `AuthorityProvider` */
public async getRequiredKeys(args: AuthorityProviderArgs): Promise<string[]> {
return convertLegacyPublicKeys((await this.fetch('/v1/chain/get_required_keys', {
transaction: args.transaction,
available_keys: args.availableKeys,
})).required_keys);
}
/** Push a serialized transaction (replaced by send_transaction, but returned format has changed) */
public async push_transaction(
{ signatures, compression = 0, serializedTransaction, serializedContextFreeData }: PushTransactionArgs
): Promise<TransactResult> {
return await this.fetch('/v1/chain/push_transaction', {
signatures,
compression,
packed_context_free_data: arrayToHex(serializedContextFreeData || new Uint8Array(0)),
packed_trx: arrayToHex(serializedTransaction),
});
}
/** Raw call to `/v1/chain/push_ro_transaction */
public async push_ro_transaction({ signatures, compression = 0, serializedTransaction }: PushTransactionArgs,
returnFailureTraces: boolean = false): Promise<ReadOnlyTransactResult> {
return await this.fetch('/v1/chain/push_ro_transaction', {
transaction: {
signatures,
compression,
packed_context_free_data: arrayToHex(new Uint8Array(0)),
packed_trx: arrayToHex(serializedTransaction),
},
return_failure_traces: returnFailureTraces,
});
}
public async push_transactions(transactions: PushTransactionArgs[]): Promise<TransactResult[]> {
const packedTrxs: PackedTrx[] = transactions.map(({signatures, compression = 0, serializedTransaction, serializedContextFreeData }: PushTransactionArgs) => {
return {
signatures,
compression,
packed_context_free_data: arrayToHex(serializedContextFreeData || new Uint8Array(0)),
packed_trx: arrayToHex(serializedTransaction),
};
});
return await this.fetch('/v1/chain/push_transactions', packedTrxs );
}
/** Send a serialized transaction */
public async send_transaction(
{ signatures, compression = 0, serializedTransaction, serializedContextFreeData }: PushTransactionArgs
): Promise<TransactResult> {
return await this.fetch('/v1/chain/send_transaction', {
signatures,
compression,
packed_context_free_data: arrayToHex(serializedContextFreeData || new Uint8Array(0)),
packed_trx: arrayToHex(serializedTransaction),
});
}
/** Raw call to `/v1/db_size/get` */
public async db_size_get(): Promise<DBSizeGetResult> { return await this.fetch('/v1/db_size/get', {}); }
/** Raw call to `/v1/trace_api/get_block` */
public async trace_get_block(block_num: number): Promise<TraceApiGetBlockResult> {
return await this.fetch('/v1/trace_api/get_block', { block_num });
}
/** Raw call to `/v1/history/get_actions` */
public async history_get_actions(accountName: string, pos: number = null, offset: number = null): Promise<GetActionsResult> {
return await this.fetch('/v1/history/get_actions', { account_name: accountName, pos, offset });
}
/** Raw call to `/v1/history/get_transaction` */
public async history_get_transaction(id: string, blockNumHint: number = null): Promise<GetTransactionResult> {
return await this.fetch('/v1/history/get_transaction', { id, block_num_hint: blockNumHint });
}
/** Raw call to `/v1/history/get_key_accounts` */
public async history_get_key_accounts(publicKey: string): Promise<GetKeyAccountsResult> {
return await this.fetch('/v1/history/get_key_accounts', { public_key: publicKey });
}
/** Raw call to `/v1/history/get_controlled_accounts` */
public async history_get_controlled_accounts(controllingAccount: string): Promise<GetControlledAccountsResult> {
return await this.fetch('/v1/history/get_controlled_accounts', { controlling_account: controllingAccount });
}
} // JsonRpc
/**
* @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 Numeric
*/
import { sha256 } from 'hash.js';
// copyright defined in zswjs/LICENSE.txt
const ripemd160 = require('./ripemd').RIPEMD160.hash as (a: Uint8Array) => ArrayBuffer;
const base58Chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
const base64Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const create_base58_map = (): number[] => {
const base58M = Array(256).fill(-1) as number[];
for (let i = 0; i < base58Chars.length; ++i) {
base58M[base58Chars.charCodeAt(i)] = i;
}
return base58M;
};
const base58Map = create_base58_map();
const create_base64_map = (): number[] => {
const base64M = Array(256).fill(-1) as number[];
for (let i = 0; i < base64Chars.length; ++i) {
base64M[base64Chars.charCodeAt(i)] = i;
}
base64M['='.charCodeAt(0)] = 0;
return base64M;
};
const base64Map = create_base64_map();
/** Is `bignum` a negative number? */
export const isNegative = (bignum: Uint8Array): boolean => {
return (bignum[bignum.length - 1] & 0x80) !== 0;
};
/** Negate `bignum` */
export const negate = (bignum: Uint8Array): void => {
let carry = 1;
for (let i = 0; i < bignum.length; ++i) {
const x = (~bignum[i] & 0xff) + carry;
bignum[i] = x;
carry = x >> 8;
}
};
/**
* Convert an unsigned decimal number in `s` to a bignum
*
* @param size bignum size (bytes)
*/
export const decimalToBinary = (size: number, s: string): Uint8Array => {
const result = new Uint8Array(size);
for (let i = 0; i < s.length; ++i) {
const srcDigit = s.charCodeAt(i);
if (srcDigit < '0'.charCodeAt(0) || srcDigit > '9'.charCodeAt(0)) {
throw new Error('invalid number');
}
let carry = srcDigit - '0'.charCodeAt(0);
for (let j = 0; j < size; ++j) {
const x = result[j] * 10 + carry;
result[j] = x;
carry = x >> 8;
}
if (carry) {
throw new Error('number is out of range');
}
}
return result;
};
/**
* Convert a signed decimal number in `s` to a bignum
*
* @param size bignum size (bytes)
*/
export const signedDecimalToBinary = (size: number, s: string): Uint8Array => {
const negative = s[0] === '-';
if (negative) {
s = s.substr(1);
}
const result = decimalToBinary(size, s);
if (negative) {
negate(result);
if (!isNegative(result)) {
throw new Error('number is out of range');
}
} else if (isNegative(result)) {
throw new Error('number is out of range');
}
return result;
};
/**
* Convert `bignum` to an unsigned decimal number
*
* @param minDigits 0-pad result to this many digits
*/
export const binaryToDecimal = (bignum: Uint8Array, minDigits = 1): string => {
const result = Array(minDigits).fill('0'.charCodeAt(0)) as number[];
for (let i = bignum.length - 1; i >= 0; --i) {
let carry = bignum[i];
for (let j = 0; j < result.length; ++j) {
const x = ((result[j] - '0'.charCodeAt(0)) << 8) + carry;
result[j] = '0'.charCodeAt(0) + x % 10;
carry = (x / 10) | 0;
}
while (carry) {
result.push('0'.charCodeAt(0) + carry % 10);
carry = (carry / 10) | 0;
}
}
result.reverse();
return String.fromCharCode(...result);
};
/**
* Convert `bignum` to a signed decimal number
*
* @param minDigits 0-pad result to this many digits
*/
export const signedBinaryToDecimal = (bignum: Uint8Array, minDigits = 1): string => {
if (isNegative(bignum)) {
const x = bignum.slice();
negate(x);
return '-' + binaryToDecimal(x, minDigits);
}
return binaryToDecimal(bignum, minDigits);
};
const base58ToBinaryVarSize = (s: string): Uint8Array => {
const result = [] as number[];
for (let i = 0; i < s.length; ++i) {
let carry = base58Map[s.charCodeAt(i)];
if (carry < 0) {
throw new Error('invalid base-58 value');
}
for (let j = 0; j < result.length; ++j) {
const x = result[j] * 58 + carry;
result[j] = x & 0xff;
carry = x >> 8;
}
if (carry) {
result.push(carry);
}
}
for (const ch of s) {
if (ch === '1') {
result.push(0);
} else {
break;
}
}
result.reverse();
return new Uint8Array(result);
};
/**
* Convert an unsigned base-58 number in `s` to a bignum
*
* @param size bignum size (bytes)
*/
export const base58ToBinary = (size: number, s: string): Uint8Array => {
if (!size) {
return base58ToBinaryVarSize(s);
}
const result = new Uint8Array(size);
for (let i = 0; i < s.length; ++i) {
let carry = base58Map[s.charCodeAt(i)];
if (carry < 0) {
throw new Error('invalid base-58 value');
}
for (let j = 0; j < size; ++j) {
const x = result[j] * 58 + carry;
result[j] = x;
carry = x >> 8;
}
if (carry) {
throw new Error('base-58 value is out of range');
}
}
result.reverse();
return result;
};
/**
* Convert `bignum` to a base-58 number
*
* @param minDigits 0-pad result to this many digits
*/
export const binaryToBase58 = (bignum: Uint8Array, minDigits = 1): string => {
const result = [] as number[];
for (const byte of bignum) {
let carry = byte;
for (let j = 0; j < result.length; ++j) {
const x = (base58Map[result[j]] << 8) + carry;
result[j] = base58Chars.charCodeAt(x % 58);
carry = (x / 58) | 0;
}
while (carry) {
result.push(base58Chars.charCodeAt(carry % 58));
carry = (carry / 58) | 0;
}
}
for (const byte of bignum) {
if (byte) {
break;
} else {
result.push('1'.charCodeAt(0));
}
}
result.reverse();
return String.fromCharCode(...result);
};
/** Convert an unsigned base-64 number in `s` to a bignum */
export const base64ToBinary = (s: string): Uint8Array => {
let len = s.length;
if ((len & 3) === 1 && s[len - 1] === '=') {
len -= 1;
} // fc appends an extra '='
if ((len & 3) !== 0) {
throw new Error('base-64 value is not padded correctly');
}
const groups = len >> 2;
let bytes = groups * 3;
if (len > 0 && s[len - 1] === '=') {
if (s[len - 2] === '=') {
bytes -= 2;
} else {
bytes -= 1;
}
}
const result = new Uint8Array(bytes);
for (let group = 0; group < groups; ++group) {
const digit0 = base64Map[s.charCodeAt(group * 4 + 0)];
const digit1 = base64Map[s.charCodeAt(group * 4 + 1)];
const digit2 = base64Map[s.charCodeAt(group * 4 + 2)];
const digit3 = base64Map[s.charCodeAt(group * 4 + 3)];
result[group * 3 + 0] = (digit0 << 2) | (digit1 >> 4);
if (group * 3 + 1 < bytes) {
result[group * 3 + 1] = ((digit1 & 15) << 4) | (digit2 >> 2);
}
if (group * 3 + 2 < bytes) {
result[group * 3 + 2] = ((digit2 & 3) << 6) | digit3;
}
}
return result;
};
/** Key types this library supports */
export enum KeyType {
k1 = 0,
r1 = 1,
wa = 2,
}
/** Public key data size, excluding type field */
export const publicKeyDataSize = 33;
/** Private key data size, excluding type field */
export const privateKeyDataSize = 32;
/** Signature data size, excluding type field */
export const signatureDataSize = 65;
/** Public key, private key, or signature in binary form */
export interface Key {
type: KeyType;
data: Uint8Array;
}
const digestSuffixRipemd160 = (data: Uint8Array, suffix: string): ArrayBuffer => {
const d = new Uint8Array(data.length + suffix.length);
for (let i = 0; i < data.length; ++i) {
d[i] = data[i];
}
for (let i = 0; i < suffix.length; ++i) {
d[data.length + i] = suffix.charCodeAt(i);
}
return ripemd160(d);
};
const stringToKey = (s: string, type: KeyType, size: number, suffix: string): Key => {
const whole = base58ToBinary(size ? size + 4 : 0, s);
const result = { type, data: new Uint8Array(whole.buffer, 0, whole.length - 4) };
const digest = new Uint8Array(digestSuffixRipemd160(result.data, suffix));
if (digest[0] !== whole[whole.length - 4] || digest[1] !== whole[whole.length - 3]
|| digest[2] !== whole[whole.length - 2] || digest[3] !== whole[whole.length - 1]) {
throw new Error('checksum doesn\'t match');
}
return result;
};
const keyToString = (key: Key, suffix: string, prefix: string): string => {
const digest = new Uint8Array(digestSuffixRipemd160(key.data, suffix));
const whole = new Uint8Array(key.data.length + 4);
for (let i = 0; i < key.data.length; ++i) {
whole[i] = key.data[i];
}
for (let i = 0; i < 4; ++i) {
whole[i + key.data.length] = digest[i];
}
return prefix + binaryToBase58(whole);
};
/** Convert key in `s` to binary form */
export const stringToPublicKey = (s: string): Key => {
if (typeof s !== 'string') {
throw new Error('expected string containing public key');
}
if (s.substr(0, 3) === 'ZSW') {
const whole = base58ToBinary(publicKeyDataSize + 4, s.substr(3));
const key = { type: KeyType.k1, data: new Uint8Array(publicKeyDataSize) };
for (let i = 0; i < publicKeyDataSize; ++i) {
key.data[i] = whole[i];
}
const digest = new Uint8Array(ripemd160(key.data));
if (digest[0] !== whole[publicKeyDataSize] || digest[1] !== whole[34]
|| digest[2] !== whole[35] || digest[3] !== whole[36]) {
throw new Error('checksum doesn\'t match');
}
return key;
} else if (s.substr(0, 7) === 'PUB_K1_') {
return stringToKey(s.substr(7), KeyType.k1, publicKeyDataSize, 'K1');
} else if (s.substr(0, 7) === 'PUB_R1_') {
return stringToKey(s.substr(7), KeyType.r1, publicKeyDataSize, 'R1');
} else if (s.substr(0, 7) === 'PUB_WA_') {
return stringToKey(s.substr(7), KeyType.wa, 0, 'WA');
} else {
throw new Error('unrecognized public key format');
}
};
/** Convert public `key` to legacy string (base-58) form */
export const publicKeyToLegacyString = (key: Key): string => {
if (key.type === KeyType.k1 && key.data.length === publicKeyDataSize) {
return keyToString(key, '', 'ZSW');
} else if (key.type === KeyType.r1 || key.type === KeyType.wa) {
throw new Error('Key format not supported in legacy conversion');
} else {
throw new Error('unrecognized public key format');
}
};
/** Convert `key` to string (base-58) form */
export const publicKeyToString = (key: Key): string => {
if (key.type === KeyType.k1 && key.data.length === publicKeyDataSize) {
return keyToString(key, 'K1', 'PUB_K1_');
} else if (key.type === KeyType.r1 && key.data.length === publicKeyDataSize) {
return keyToString(key, 'R1', 'PUB_R1_');
} else if (key.type === KeyType.wa) {
return keyToString(key, 'WA', 'PUB_WA_');
} else {
throw new Error('unrecognized public key format');
}
};
/** If a key is in the legacy format (`ZSW` prefix), then convert it to the new format (`PUB_K1_`).
* Leaves other formats untouched
*/
export const convertLegacyPublicKey = (s: string): string => {
if (s.substr(0, 3) === 'ZSW') {
return publicKeyToString(stringToPublicKey(s));
}
return s;
};
/** If a key is in the legacy format (`ZSW` prefix), then convert it to the new format (`PUB_K1_`).
* Leaves other formats untouched
*/
export const convertLegacyPublicKeys = (keys: string[]): string[] => {
return keys.map(convertLegacyPublicKey);
};
/** Convert key in `s` to binary form */
export const stringToPrivateKey = (s: string): Key => {
if (typeof s !== 'string') {
throw new Error('expected string containing private key');
}
if (s.substr(0, 7) === 'PVT_R1_') {
return stringToKey(s.substr(7), KeyType.r1, privateKeyDataSize, 'R1');
} else if (s.substr(0, 7) === 'PVT_K1_') {
return stringToKey(s.substr(7), KeyType.k1, privateKeyDataSize, 'K1');
} else {
// todo: Verify checksum: sha256(sha256(key.data)).
// Not critical since a bad key will fail to produce a
// valid signature anyway.
const whole = base58ToBinary(privateKeyDataSize + 5, s);
const key = { type: KeyType.k1, data: new Uint8Array(privateKeyDataSize) };
if (whole[0] !== 0x80) {
throw new Error('unrecognized private key type');
}
for (let i = 0; i < privateKeyDataSize; ++i) {
key.data[i] = whole[i + 1];
}
return key;
}
};
/** Convert private `key` to legacy string (base-58) form */
export const privateKeyToLegacyString = (key: Key): string => {
if (key.type === KeyType.k1 && key.data.length === privateKeyDataSize) {
const whole = [] as number[];
whole.push(128);
key.data.forEach((byte) => whole.push(byte));
const digest = new Uint8Array(
sha256().update(
sha256().update(whole).digest()
).digest()
);
const result = new Uint8Array(privateKeyDataSize + 5);
for (let i = 0; i < whole.length; i++) {
result[i] = whole[i];
}
for (let i = 0; i < 4; i++) {
result[i + whole.length] = digest[i];
}
return binaryToBase58(result);
} else if (key.type === KeyType.r1 || key.type === KeyType.wa) {
throw new Error('Key format not supported in legacy conversion');
} else {
throw new Error('unrecognized public key format');
}
};
/** Convert `key` to string (base-58) form */
export const privateKeyToString = (key: Key): string => {
if (key.type === KeyType.r1) {
return keyToString(key, 'R1', 'PVT_R1_');
} else if (key.type === KeyType.k1) {
return keyToString(key, 'K1', 'PVT_K1_');
} else {
throw new Error('unrecognized private key format');
}
};
/** Convert key in `s` to binary form */
export const stringToSignature = (s: string): Key => {
if (typeof s !== 'string') {
throw new Error('expected string containing signature');
}
if (s.substr(0, 7) === 'SIG_K1_') {
return stringToKey(s.substr(7), KeyType.k1, signatureDataSize, 'K1');
} else if (s.substr(0, 7) === 'SIG_R1_') {
return stringToKey(s.substr(7), KeyType.r1, signatureDataSize, 'R1');
} else if (s.substr(0, 7) === 'SIG_WA_') {
return stringToKey(s.substr(7), KeyType.wa, 0, 'WA');
} else {
throw new Error('unrecognized signature format');
}
};
/** Convert `signature` to string (base-58) form */
export const signatureToString = (signature: Key): string => {
if (signature.type === KeyType.k1) {
return keyToString(signature, 'K1', 'SIG_K1_');
} else if (signature.type === KeyType.r1) {
return keyToString(signature, 'R1', 'SIG_R1_');
} else if (signature.type === KeyType.wa) {
return keyToString(signature, 'WA', 'SIG_WA_');
} else {
throw new Error('unrecognized signature format');
}
};
/**
* @module RPC-API-Methods
* copyright defined in zswjs/LICENSE.txt
*/
import { TransactionReceiptHeader, TransactionTrace } from './zswjs-api-interfaces';
import { Authorization } from './zswjs-serialize';
/** Structured format for abis */
export interface Abi {
version: string;
types: { new_type_name: string, type: string }[];
structs: { name: string, base: string, fields: { name: string, type: string }[] }[];
actions: { name: string, type: string, ricardian_contract: string }[];
tables: { name: string, type: string, index_type: string, key_names: string[], key_types: string[] }[];
ricardian_clauses: { id: string, body: string }[];
error_messages: { error_code: number, error_msg: string }[];
abi_extensions: { tag: number, value: string }[];
variants?: { name: string, types: string[] }[];
action_results?: { name: string, result_type: string }[],
kv_tables?: { [key: string]: { type: string, primary_index: { name: string, type: string }, secondary_indices: { [key: string]: { type: string }}[] } }[],
}
export interface BlockHeader {
timestamp: string;
producer: string;
confirmed: number;
previous: string;
transaction_mroot: string;
action_mroot: string;
schedule_version: number;
new_producers?: ProducerScheduleType;
header_extensions: [number, string][];
}
export interface SignedBlockHeader extends BlockHeader {
producer_signature: string;
}
export interface AccountResourceInfo {
used: number;
available: number;
max: number;
last_usage_update_time?: string;
current_used?: number;
}
export interface ResourceOverview {
owner: string;
ram_bytes: number;
net_weight: string;
cpu_weight: string;
}
export interface ResourceDelegation {
from: string;
to: string;
net_weight: string;
cpu_weight: string;
}
export interface RefundRequest {
owner: string;
request_time: string;
net_amount: string;
cpu_amount: string;
}
export interface VoterInfo {
owner: string;
proxy: string;
producers: string[];
staked: number;
last_vote_weight: string;
proxied_vote_weight: string;
is_proxy: number;
flags1: number;
reserved2: number;
reserved3: string;
}
export interface RexBalance {
version: number;
owner: string;
vote_stake: string;
rex_balance: string;
matured_rex: number;
rex_maturities: any;
}
export interface Authority {
threshold: number;
keys: KeyWeight[];
accounts: PermissionLevelWeight[];
waits: WaitWeight[];
}
export interface KeyWeight {
key: string;
weight: number;
}
export interface Permission {
perm_name: string;
parent: string;
required_auth: Authority;
}
export interface PermissionLevel {
actor: string;
permission: string;
}
export interface PermissionLevelWeight {
permission: PermissionLevel;
weight: number;
}
export interface WaitWeight {
wait_sec: number;
weight: number;
}
/** Return value of `/v1/chain/abi_bin_to_json` */
export interface AbiBinToJsonResult {
args: 'any'
}
/** Return value of `/v1/chain/abi_json_to_bin` */
export interface AbiJsonToBinResult {
binargs: 'string'
}
/** Return value of `/v1/chain/get_abi` */
export interface GetAbiResult {
account_name: string;
abi?: Abi;
}
/** Return value of `/v1/chain/get_account` */
export interface GetAccountResult {
account_name: string;
head_block_num: number;
head_block_time: string;
privileged: boolean;
last_code_update: string;
created: string;
core_liquid_balance?: string;
ram_quota: number;
net_weight: number;
cpu_weight: number;
net_limit: AccountResourceInfo;
cpu_limit: AccountResourceInfo;
ram_usage: number;
permissions: Permission[];
total_resources: ResourceOverview|null;
self_delegated_bandwidth: ResourceDelegation|null;
refund_request: RefundRequest|null;
voter_info: any;
rex_info: any;
}
export interface AccountResult {
account_name: string;
permission_name: string;
authorizing_account?: Authorization;
authorizing_key?: string;
weight: number;
threshold: number;
}
/** Return value of `/v1/chain/get_accounts_by_authorizers` */
export interface GetAccountsByAuthorizersResult {
accounts: AccountResult[];
}
export interface GetActivatedProtocolFeaturesParams {
limit?: number;
search_by_block_num?: boolean;
reverse?: boolean;
lower_bound?: number;
upper_bound?: number;
}
export interface ActivatedProtocolFeature {
feature_digest: string;
activation_ordinal: number;
activation_block_num: number;
description_digest: string;
dependencies: string[];
protocol_feature_type: string;
specification: { name: string, value: string, };
}
/** Return value of `/v1/chain/get_activated_protocol_features` */
export interface GetActivatedProtocolFeaturesResult {
activated_protocol_features: ActivatedProtocolFeature[];
more?: number;
}
/** Return value of `/v1/chain/get_block_info` */
export interface GetBlockInfoResult {
timestamp: string;
producer: string;
confirmed: number;
previous: string;
transaction_mroot: string;
action_mroot: string;
schedule_version: number;
producer_signature: string;
id: string;
block_num: number;
ref_block_num: number;
ref_block_prefix: number;
}
/** Returned action from nodzsw, data is optional */
export interface ProcessedAction {
account: string;
name: string;
authorization: Authorization[];
data?: any;
hex_data?: string;
}
export interface ProcessedTransaction {
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?: ProcessedAction[];
context_free_data?: Uint8Array[];
actions: ProcessedAction[];
transaction_extensions?: [number, string][];
}
export interface PackedTransaction {
id: string;
signatures: string[];
compression: number|string;
packed_context_free_data: string;
context_free_data: string[];
packed_trx: string;
transaction: ProcessedTransaction;
}
export interface PackedTrx {
signatures: string[];
compression: number;
packed_trx: string;
packed_context_free_data: string;
}
export interface TransactionReceipt extends TransactionReceiptHeader {
trx: PackedTransaction;
}
/** Return value of `/v1/chain/get_block` */
export interface GetBlockResult {
timestamp: string;
producer: string;
confirmed: number;
previous: string;
transaction_mroot: string;
action_mroot: string;
schedule_version: number;
new_producers: ProducerScheduleType|null;
producer_signature: string;
transactions: any;
id: string;
block_num: number;
ref_block_prefix: number;
}
/** Used to calculate TAPoS fields in transactions */
export interface BlockTaposInfo {
block_num: number;
id: string;
timestamp?: string;
header?: BlockHeader;
}
export interface ProducerKey {
producer_name: string;
block_signing_key: string;
}
export interface BlockSigningAuthority {
threshold: number;
keys: KeyWeight[];
}
export interface ProducerAuthority {
producer_name: string;
authority: [ number|string, BlockSigningAuthority];
};
export interface ProducerAuthoritySchedule {
version: number;
producers: ProducerAuthority[];
}
export interface ProducerScheduleType {
version: number;
producers: ProducerKey[];
}
export interface ScheduleInfo {
schedule_lib_num: number;
schedule_hash: string;
schedule: ProducerScheduleType;
}
export interface IncrementalMerkle {
_active_nodes: string[];
_node_count: number;
}
export interface ProtocolFeatureActivationSet {
protocol_features: string[]
}
export interface SecurityGroupInfo {
version: number;
participants: string[];
}
export interface StateExtension {
security_group_info: SecurityGroupInfo
}
/** Return value of `/v1/chain/get_block_header_state` */
export interface GetBlockHeaderStateResult {
id: string;
header: SignedBlockHeader;
pending_schedule: ScheduleInfo;
activated_protocol_features: ProtocolFeatureActivationSet;
additional_signatures: string[];
block_num: number;
dpos_proposed_irreversible_blocknum: number;
dpos_irreversible_blocknum: number;
active_schedule: ProducerAuthoritySchedule;
blockroot_merkle: IncrementalMerkle;
producer_to_last_produced: Map<string, number>;
producer_to_last_implied_irb: Map<string, number>;
// valid_block_signing_authority: BlockSigningAuthority;
valid_block_signing_authority: any;
confirm_count: number[];
state_extension: [number, StateExtension];
}
/** Subset of `GetBlockHeaderStateResult` used to calculate TAPoS fields in transactions */
export interface BlockHeaderStateTaposInfo {
block_num: number;
id: string;
header: SignedBlockHeader;
}
/** Return value of `/v1/chain/get_code` */
export interface GetCodeResult {
account_name: string;
code_hash: string;
wast: string;
wasm: string;
abi?: Abi;
}
/** Return value of `/v1/chain/get_code_hash` */
export interface GetCodeHashResult {
account_name: string;
code_hash: string;
}
/** Return value of `/v1/chain/get_currency_stats` */
export interface GetCurrencyStatsResult {
[key: string]: {
supply: string;
max_supply: string;
issuer: string;
}
}
/** Return value of `/v1/chain/get_info` */
export interface GetInfoResult {
server_version: string;
chain_id: string;
head_block_num: number;
last_irreversible_block_num: number;
last_irreversible_block_id: string;
last_irreversible_block_time?: string;
head_block_id: string;
head_block_time: string;
head_block_producer: string;
virtual_block_cpu_limit: number;
virtual_block_net_limit: number;
block_cpu_limit: number;
block_net_limit: number;
server_version_string?: string;
fork_db_head_block_num?: number;
fork_db_head_block_id?: string;
server_full_version_string?: string;
first_block_num?: number;
}
/** Return value of /v1/chain/get_producer_schedule */
export interface GetProducerScheduleResult {
active: ProducerAuthoritySchedule|null;
pending: ProducerAuthoritySchedule|null;
proposed: ProducerAuthoritySchedule|null;
}
export interface ProducerDetails {
owner: string;
producer_authority?: any[];
url: string;
is_active?: number;
total_votes: string;
producer_key: string;
unpaid_blocks?: number;
last_claim_time?: string;
location?: number;
}
/** Return value of `/v1/chain/get_producers` */
export interface GetProducersResult {
rows: ProducerDetails[];
total_producer_vote_weight: string;
more: string;
}
/** Return value of `/v1/chain/get_raw_code_and_abi` */
export interface GetRawCodeAndAbiResult {
account_name: string;
wasm: string;
abi: string;
}
/** Return value of `/v1/chain/get_raw_abi` */
export interface GetRawAbiResult {
account_name: string;
code_hash: string;
abi_hash: string;
abi: string;
}
export interface DeferredTransaction extends ProcessedTransaction {
deferred_transaction_generation?: {
sender_trx_id: string;
sender_id: string;
sender: string;
}
}
export interface GeneratedTransaction {
trx_id: string;
sender: string;
sender_id: string;
payer: string;
delay_until: string;
expiration: string;
published: string;
packed_trx?: string[];
transaction?: DeferredTransaction[]
}
/** Return value of `/v1/chain/get_scheduled_transactions` */
export interface GetScheduledTransactionsResult {
transactions: GeneratedTransaction[];
more: string;
}
/** Return value of `/v1/chain/get_table_rows` and `/v1/chain/get_kv_table_rows` */
export interface GetTableRowsResult {
rows: any[];
more: boolean;
next_key: string;
next_key_bytes: string;
}
export interface GetTableByScopeResultRow {
code: string;
scope: string;
table: string;
payer: string;
count: number;
}
/** Return value of `/v1/chain/get_table_by_scope` */
export interface GetTableByScopeResult {
rows: GetTableByScopeResultRow[];
more: string;
}
/** Arguments for `push_transaction` */
export interface PushTransactionArgs {
signatures: string[];
compression?: number;
serializedTransaction: Uint8Array;
serializedContextFreeData?: Uint8Array;
}
/** Return value of `/v1/chain/push_ro_transaction` */
export interface ReadOnlyTransactResult {
head_block_num: number;
head_block_id: string;
last_irreversible_block_num: number;
last_irreversible_block_id: string;
code_hash: string;
pending_transactions: string[];
result: TransactionTrace;
}
export interface DBSizeIndexCount {
index: string;
row_count: number;
}
/** Return value of `/v1/db_size/get` */
export interface DBSizeGetResult {
free_bytes: number;
used_bytes: number;
size: number;
indices: DBSizeIndexCount[];
}
export interface TraceApiAction {
global_sequence: number;
receiver: string;
account: string;
action: string;
authorization: Authorization[];
data: any;
return_value: any;
}
export interface TraceApiTransactionHeader {
expiration: string;
ref_block_num: number;
ref_block_prefix: number;
max_net_usage_words: number;
max_cpu_usage_ms: number;
delay_sec: number;
}
export interface TraceApiTransaction {
id: string;
actions: TraceApiAction[];
status?: string;
cpu_usage_us?: number;
net_usage_words?: number;
signatures?: string[];
transaction_header?: TraceApiTransactionHeader,
bill_to_accounts: string[]
}
/** Return value of `/v1/trace_api/get_block` */
export interface TraceApiGetBlockResult {
id: string;
number: number;
previous_id: string;
status: string;
timestamp: string;
producer: string;
transaction_mroot?: string;
action_mroot?: string;
schedule_version: number;
transactions: TraceApiTransaction;
}
export interface OrderedActionResult {
global_action_seq: number;
account_action_seq: number;
block_num: number;
block_time: string;
action_trace: any;
}
/** Return value of `/v1/history/get_actions` */
export interface GetActionsResult {
actions: OrderedActionResult[];
last_irreversible_block: number;
time_limit_exceeded_error?: boolean;
}
/** Return value of `/v1/history/get_transaction` */
export interface GetTransactionResult {
id: string;
trx: any;
block_time: string;
block_num: number;
last_irreversible_block: number;
traces: any[];
}
/** Return value of `/v1/history/get_key_accounts` */
export interface GetKeyAccountsResult {
account_names: string[];
}
/** Return value of `/v1/history/get_controlled_accounts` */
export interface GetControlledAccountsResult {
controlled_accounts: string[];
}
/**
* @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 Serialize
*/
// copyright defined in zswjs/LICENSE.txt
/* eslint-disable max-classes-per-file */
/* eslint-disable jsdoc/check-indentation */
import * as numeric from './zswjs-numeric';
import { TransactionHeader } from './zswjs-api-interfaces';
import { Abi, BlockTaposInfo } from './zswjs-rpc-interfaces';
import { Query } from './zswjs-api-interfaces';
/** A field in an abi */
export interface Field {
/** Field name */
name: string;
/** Type name in string form */
typeName: string;
/** Type of the field */
type: Type;
}
/** Options for serialize() and deserialize() */
export interface SerializerOptions {
bytesAsUint8Array?: boolean;
}
/** State for serialize() and deserialize() */
export class SerializerState {
public options: SerializerOptions;
/** Have any binary extensions been skipped? */
public skippedBinaryExtension = false;
constructor(options: SerializerOptions = {}) {
this.options = options;
}
}
/**
* An Anyvar (non-short form) may be any of the following:
* * null
* * string
* * number
* * Caution: assumes number is int32. Use {type, value} form for other numeric types
* * an array of anyvar
* * {type, value}
* * type is a string matching one of the predefined types in anyvarDefs
* * value:
* * If type === 'any_object', then value is an object. The values within the object are anyvar.
* * If type === 'any_array', then value is an array of anyvar.
* * Else, value must be zswjs-compatible with the specified type (e.g. uint64 should be a string
* containing the value in decimal).
* * Other object. The values within the object are anyvar.
*
* The short form is more convenient, but it can't be converted back to binary (serialized).
* Wherever the anyvar would have {type, value}, it has just the value instead.
*/
export type Anyvar = null | string | number | Anyvar[] | { type: string, value: any } | Record<string, unknown>;
interface AnyvarDef {
index: number;
useShortForm: boolean;
type: {
name: string,
serialize(buffer: SerialBuffer, value: any): void,
deserialize(buffer: SerialBuffer, state?: SerializerState): any,
};
}
/** A type in an abi */
export interface Type {
/** Type name */
name: string;
/** Type name this is an alias of, if any */
aliasOfName: string;
/** Type this is an array of, if any */
arrayOf: Type;
/** Type this is an optional of, if any */
optionalOf: Type;
/** Marks binary extension fields */
extensionOf?: Type;
/** Base name of this type, if this is a struct */
baseName: string;
/** Base of this type, if this is a struct */
base: Type;
/** Contained fields, if this is a struct */
fields: Field[];
/** Convert `data` to binary form and store in `buffer` */
serialize: (buffer: SerialBuffer, data: any, state?: SerializerState, allowExtensions?: boolean) => void;
/** Convert data in `buffer` from binary form */
deserialize: (buffer: SerialBuffer, state?: SerializerState, allowExtensions?: boolean) => any;
}
/** Structural representation of a symbol */
export interface Symbol {
/** Name of the symbol, not including precision */
name: string;
/** Number of digits after the decimal point */
precision: number;
}
export interface Contract {
actions: Map<string, Type>;
types: Map<string, Type>;
}
export interface Authorization {
actor: string;
permission: string;
}
/** Action with data in structured form */
export interface Action {
account: string;
name: string;
authorization: Authorization[];
data: any;
hex_data?: string;
}
/** Action with data in serialized hex form */
export interface SerializedAction {
account: string;
name: string;
authorization: Authorization[];
data: string;
}
/** Serialize and deserialize data */
export class SerialBuffer {
/** Amount of valid data in `array` */
public length: number;
/** Data in serialized (binary) form */
public array: Uint8Array;
/** Current position while reading (deserializing) */
public readPos = 0;
public textEncoder: TextEncoder;
public textDecoder: TextDecoder;
/**
* @param __namedParameters
* `array`: `null` if serializing, or binary data to deserialize
* `textEncoder`: `TextEncoder` instance to use. Pass in `null` if running in a browser
* `textDecoder`: `TextDecider` instance to use. Pass in `null` if running in a browser
*/
constructor({ textEncoder, textDecoder, array } = {} as
{ textEncoder?: TextEncoder, textDecoder?: TextDecoder, array?: Uint8Array }) {
this.array = array || new Uint8Array(1024);
this.length = array ? array.length : 0;
this.textEncoder = textEncoder || new TextEncoder();
this.textDecoder = textDecoder || new TextDecoder('utf-8', { fatal: true });
}
/** Resize `array` if needed to have at least `size` bytes free */
public reserve(size: number): void {
if (this.length + size <= this.array.length) {
return;
}
let l = this.array.length;
while (this.length + size > l) {
l = Math.ceil(l * 1.5);
}
const newArray = new Uint8Array(l);
newArray.set(this.array);
this.array = newArray;
}
/** Is there data available to read? */
public haveReadData(): boolean {
return this.readPos < this.length;
}
/** Restart reading from the beginning */
public restartRead(): void {
this.readPos = 0;
}
/** Return data with excess storage trimmed away */
public asUint8Array(): Uint8Array {
return new Uint8Array(this.array.buffer, this.array.byteOffset, this.length);
}
/** Append bytes */
public pushArray(v: number[] | Uint8Array): void {
this.reserve(v.length);
this.array.set(v, this.length);
this.length += v.length;
}
/** Append bytes */
public push(...v: number[]): void {
this.pushArray(v);
}
/** Get a single byte */
public get(): number {
if (this.readPos < this.length) {
return this.array[this.readPos++];
}
throw new Error('Read past end of buffer');
}
/** Append bytes in `v`. Throws if `len` doesn't match `v.length` */
public pushUint8ArrayChecked(v: Uint8Array, len: number): void {
if (v.length !== len) {
throw new Error('Binary data has incorrect size');
}
this.pushArray(v);
}
/** Get `len` bytes */
public getUint8Array(len: number): Uint8Array {
if (this.readPos + len > this.length) {
throw new Error('Read past end of buffer');
}
const result = new Uint8Array(this.array.buffer, this.array.byteOffset + this.readPos, len);
this.readPos += len;
return result;
}
/** Skip `len` bytes */
public skip(len: number): void {
if (this.readPos + len > this.length) {
throw new Error('Read past end of buffer');
}
this.readPos += len;
}
/** Append a `uint16` */
public pushUint16(v: number): void {
this.push((v >> 0) & 0xff, (v >> 8) & 0xff);
}
/** Get a `uint16` */
public getUint16(): number {
let v = 0;
v |= this.get() << 0;
v |= this.get() << 8;
return v;
}
/** Append a `uint32` */
public pushUint32(v: number): void {
this.push((v >> 0) & 0xff, (v >> 8) & 0xff, (v >> 16) & 0xff, (v >> 24) & 0xff);
}
/** Get a `uint32` */
public getUint32(): number {
let v = 0;
v |= this.get() << 0;
v |= this.get() << 8;
v |= this.get() << 16;
v |= this.get() << 24;
return v >>> 0;
}
/** Append a `uint64`. *Caution*: `number` only has 53 bits of precision */
public pushNumberAsUint64(v: number): void {
this.pushUint32(v >>> 0);
this.pushUint32(Math.floor(v / 0x10000_0000) >>> 0);
}
/**
* Get a `uint64` as a `number`. *Caution*: `number` only has 53 bits of precision; some values will change.
* `numeric.binaryToDecimal(serialBuffer.getUint8Array(8))` recommended instead
*/
public getUint64AsNumber(): number {
const low = this.getUint32();
const high = this.getUint32();
return (high >>> 0) * 0x10000_0000 + (low >>> 0);
}
/** Append a `varuint32` */
public pushVaruint32(v: number): void {
while (true) {
if (v >>> 7) {
this.push(0x80 | (v & 0x7f));
v = v >>> 7;
} else {
this.push(v);
break;
}
}
}
/** Get a `varuint32` */
public getVaruint32(): number {
let v = 0;
let bit = 0;
while (true) {
const b = this.get();
v |= (b & 0x7f) << bit;
bit += 7;
if (!(b & 0x80)) {
break;
}
}
return v >>> 0;
}
/** Append a `varint32` */
public pushVarint32(v: number): void {
this.pushVaruint32((v << 1) ^ (v >> 31));
}
/** Get a `varint32` */
public getVarint32(): number {
const v = this.getVaruint32();
if (v & 1) {
return ((~v) >> 1) | 0x8000_0000;
} else {
return v >>> 1;
}
}
/** Append a `float32` */
public pushFloat32(v: number): void {
this.pushArray(new Uint8Array((new Float32Array([v])).buffer));
}
/** Get a `float32` */
public getFloat32(): number {
return new Float32Array(this.getUint8Array(4).slice().buffer)[0];
}
/** Append a `float64` */
public pushFloat64(v: number): void {
this.pushArray(new Uint8Array((new Float64Array([v])).buffer));
}
/** Get a `float64` */
public getFloat64(): number {
return new Float64Array(this.getUint8Array(8).slice().buffer)[0];
}
/** Append a `name` */
public pushName(s: string): void {
if (typeof s !== 'string') {
throw new Error('Expected string containing name');
}
const regex = new RegExp(/^[.1-5a-z]{0,12}[.1-5a-j]?$/);
if (!regex.test(s)) {
throw new Error('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'); // eslint-disable-line
}
const charToSymbol = (c: number): number => {
if (c >= 'a'.charCodeAt(0) && c <= 'z'.charCodeAt(0)) {
return (c - 'a'.charCodeAt(0)) + 6;
}
if (c >= '1'.charCodeAt(0) && c <= '5'.charCodeAt(0)) {
return (c - '1'.charCodeAt(0)) + 1;
}
return 0;
};
const a = new Uint8Array(8);
let bit = 63;
for (let i = 0; i < s.length; ++i) {
let c = charToSymbol(s.charCodeAt(i));
if (bit < 5) {
c = c << 1;
}
for (let j = 4; j >= 0; --j) {
if (bit >= 0) {
a[Math.floor(bit / 8)] |= ((c >> j) & 1) << (bit % 8);
--bit;
}
}
}
this.pushArray(a);
}
/** Get a `name` */
public getName(): string {
const a = this.getUint8Array(8);
let result = '';
for (let bit = 63; bit >= 0;) {
let c = 0;
for (let i = 0; i < 5; ++i) {
if (bit >= 0) {
c = (c << 1) | ((a[Math.floor(bit / 8)] >> (bit % 8)) & 1);
--bit;
}
}
if (c >= 6) {
result += String.fromCharCode(c + 'a'.charCodeAt(0) - 6);
} else if (c >= 1) {
result += String.fromCharCode(c + '1'.charCodeAt(0) - 1);
} else {
result += '.';
}
}
while (result.endsWith('.')) {
result = result.substr(0, result.length - 1);
}
return result;
}
/** Append length-prefixed binary data */
public pushBytes(v: number[] | Uint8Array): void {
this.pushVaruint32(v.length);
this.pushArray(v);
}
/** Get length-prefixed binary data */
public getBytes(): Uint8Array {
return this.getUint8Array(this.getVaruint32());
}
/** Append a string */
public pushString(v: string): void {
this.pushBytes(this.textEncoder.encode(v));
}
/** Get a string */
public getString(): string {
return this.textDecoder.decode(this.getBytes());
}
/** Append a `symbol_code`. Unlike `symbol`, `symbol_code` doesn't include a precision. */
public pushSymbolCode(name: string): void {
if (typeof name !== 'string') {
throw new Error('Expected string containing symbol_code');
}
const a = [];
a.push(...this.textEncoder.encode(name));
while (a.length < 8) {
a.push(0);
}
this.pushArray(a.slice(0, 8));
}
/** Get a `symbol_code`. Unlike `symbol`, `symbol_code` doesn't include a precision. */
public getSymbolCode(): string {
const a = this.getUint8Array(8);
let len;
for (len = 0; len < a.length; ++len) {
if (!a[len]) {
break;
}
}
const name = this.textDecoder.decode(new Uint8Array(a.buffer, a.byteOffset, len));
return name;
}
/** Append a `symbol` */
public pushSymbol({ name, precision }: { name: string, precision: number }): void {
if (!/^[A-Z]{1,7}$/.test(name)) {
throw new Error('Expected symbol to be A-Z and between one and seven characters');
}
const a = [precision & 0xff];
a.push(...this.textEncoder.encode(name));
while (a.length < 8) {
a.push(0);
}
this.pushArray(a.slice(0, 8));
}
/** Get a `symbol` */
public getSymbol(): { name: string, precision: number } {
const precision = this.get();
const a = this.getUint8Array(7);
let len;
for (len = 0; len < a.length; ++len) {
if (!a[len]) {
break;
}
}
const name = this.textDecoder.decode(new Uint8Array(a.buffer, a.byteOffset, len));
return { name, precision };
}
/** Append an asset */
public pushAsset(s: string): void {
if (typeof s !== 'string') {
throw new Error('Expected string containing asset');
}
s = s.trim();
let pos = 0;
let amount = '';
let precision = 0;
if (s[pos] === '-') {
amount += '-';
++pos;
}
let foundDigit = false;
while (pos < s.length && s.charCodeAt(pos) >= '0'.charCodeAt(0) && s.charCodeAt(pos) <= '9'.charCodeAt(0)) {
foundDigit = true;
amount += s[pos];
++pos;
}
if (!foundDigit) {
throw new Error('Asset must begin with a number');
}
if (s[pos] === '.') {
++pos;
while (pos < s.length && s.charCodeAt(pos) >= '0'.charCodeAt(0) && s.charCodeAt(pos) <= '9'.charCodeAt(0)) {
amount += s[pos];
++precision;
++pos;
}
}
const name = s.substr(pos).trim();
this.pushArray(numeric.signedDecimalToBinary(8, amount));
this.pushSymbol({ name, precision });
}
/** Get an asset */
public getAsset(): string {
const amount = this.getUint8Array(8);
const { name, precision } = this.getSymbol();
let s = numeric.signedBinaryToDecimal(amount, precision + 1);
if (precision) {
s = s.substr(0, s.length - precision) + '.' + s.substr(s.length - precision);
}
return s + ' ' + name;
}
/** Append a public key */
public pushPublicKey(s: string): void {
const key = numeric.stringToPublicKey(s);
this.push(key.type);
this.pushArray(key.data);
}
/** Get a public key */
public getPublicKey(): string {
const type = this.get();
let data: Uint8Array;
if (type === numeric.KeyType.wa) {
const begin = this.readPos;
this.skip(34);
this.skip(this.getVaruint32());
data = new Uint8Array(this.array.buffer, this.array.byteOffset + begin, this.readPos - begin);
} else {
data = this.getUint8Array(numeric.publicKeyDataSize);
}
return numeric.publicKeyToString({ type, data });
}
/** Append a private key */
public pushPrivateKey(s: string): void {
const key = numeric.stringToPrivateKey(s);
this.push(key.type);
this.pushArray(key.data);
}
/** Get a private key */
public getPrivateKey(): string {
const type = this.get();
const data = this.getUint8Array(numeric.privateKeyDataSize);
return numeric.privateKeyToString({ type, data });
}
/** Append a signature */
public pushSignature(s: string): void {
const key = numeric.stringToSignature(s);
this.push(key.type);
this.pushArray(key.data);
}
/** Get a signature */
public getSignature(): string {
const type = this.get();
let data: Uint8Array;
if (type === numeric.KeyType.wa) {
const begin = this.readPos;
this.skip(65);
this.skip(this.getVaruint32());
this.skip(this.getVaruint32());
data = new Uint8Array(this.array.buffer, this.array.byteOffset + begin, this.readPos - begin);
} else {
data = this.getUint8Array(numeric.signatureDataSize);
}
return numeric.signatureToString({ type, data });
}
} // SerialBuffer
/** Is this a supported ABI version? */
export const supportedAbiVersion = (version: string): boolean => {
return version.startsWith('zswchain::abi/1.') || version.indexOf('::abi/1.')===5;
};
const checkDateParse = (date: string): number => {
const result = Date.parse(date);
if (Number.isNaN(result)) {
throw new Error('Invalid time format');
}
return result;
};
/** Convert date in ISO format to `time_point` (miliseconds since epoch) */
export const dateToTimePoint = (date: string): number => {
return Math.round(checkDateParse(date + 'Z') * 1000);
};
/** Convert `time_point` (miliseconds since epoch) to date in ISO format */
export const timePointToDate = (us: number): string => {
const s = (new Date(us / 1000)).toISOString();
return s.substr(0, s.length - 1);
};
/** Convert date in ISO format to `time_point_sec` (seconds since epoch) */
export const dateToTimePointSec = (date: string): number => {
return Math.round(checkDateParse(date + 'Z') / 1000);
};
/** Convert `time_point_sec` (seconds since epoch) to to date in ISO format */
export const timePointSecToDate = (sec: number): string => {
const s = (new Date(sec * 1000)).toISOString();
return s.substr(0, s.length - 1);
};
/** Convert date in ISO format to `block_timestamp_type` (half-seconds since a different epoch) */
export const dateToBlockTimestamp = (date: string): number => {
return Math.round((checkDateParse(date + 'Z') - 946684800000) / 500);
};
/** Convert `block_timestamp_type` (half-seconds since a different epoch) to to date in ISO format */
export const blockTimestampToDate = (slot: number): string => {
const s = (new Date(slot * 500 + 946684800000)).toISOString();
return s.substr(0, s.length - 1);
};
/** Convert `string` to `Symbol`. format: `precision,NAME`. */
export const stringToSymbol = (s: string): { name: string, precision: number } => {
if (typeof s !== 'string') {
throw new Error('Expected string containing symbol');
}
const m = s.match(/^([0-9]+),([A-Z]+)$/);
if (!m) {
throw new Error('Invalid symbol');
}
return { name: m[2], precision: +m[1] };
};
/** Convert `Symbol` to `string`. format: `precision,NAME`. */
export const symbolToString = ({ name, precision }: { name: string, precision: number }): string => {
return precision + ',' + name;
};
/** Convert binary data to hex */
export const arrayToHex = (data: Uint8Array): string => {
let result = '';
for (const x of data) {
result += ('00' + x.toString(16)).slice(-2);
}
return result.toUpperCase();
};
/** Convert hex to binary data */
export const hexToUint8Array = (hex: string): Uint8Array => {
if (typeof hex !== 'string') {
throw new Error('Expected string containing hex digits');
}
if (hex.length % 2) {
throw new Error('Odd number of hex digits');
}
const l = hex.length / 2;
const result = new Uint8Array(l);
for (let i = 0; i < l; ++i) {
const x = parseInt(hex.substr(i * 2, 2), 16);
if (Number.isNaN(x)) {
throw new Error('Expected hex string');
}
result[i] = x;
}
return result;
};
function serializeUnknown(buffer: SerialBuffer, data: any): SerialBuffer {
throw new Error('Don\'t know how to serialize ' + this.name);
}
function deserializeUnknown(buffer: SerialBuffer): SerialBuffer {
throw new Error('Don\'t know how to deserialize ' + this.name);
}
function serializeStruct(
this: Type, buffer: SerialBuffer, data: any, state = new SerializerState(), allowExtensions = true
): void {
if (typeof data !== 'object') {
throw new Error('expected object containing data: ' + JSON.stringify(data));
}
if (this.base) {
this.base.serialize(buffer, data, state, allowExtensions);
}
for (const field of this.fields) {
if (field.name in data) {
if (state.skippedBinaryExtension) {
throw new Error('unexpected ' + this.name + '.' + field.name);
}
field.type.serialize(
buffer, data[field.name], state, allowExtensions && field === this.fields[this.fields.length - 1]);
} else {
if (allowExtensions && field.type.extensionOf) {
state.skippedBinaryExtension = true;
} else {
throw new Error('missing ' + this.name + '.' + field.name + ' (type=' + field.type.name + ')');
}
}
}
}
function deserializeStruct(this: Type, buffer: SerialBuffer, state = new SerializerState(), allowExtensions = true): any {
let result;
if (this.base) {
result = this.base.deserialize(buffer, state, allowExtensions);
} else {
result = {};
}
for (const field of this.fields) {
if (allowExtensions && field.type.extensionOf && !buffer.haveReadData()) {
state.skippedBinaryExtension = true;
} else {
result[field.name] = field.type.deserialize(buffer, state, allowExtensions);
}
}
return result;
}
function serializeVariant(
this: Type, buffer: SerialBuffer, data: any, state?: SerializerState, allowExtensions?: boolean
): void {
if (!Array.isArray(data) || data.length !== 2 || typeof data[0] !== 'string') {
throw new Error('expected variant: ["type", value]');
}
const i = this.fields.findIndex((field: Field) => field.name === data[0]);
if (i < 0) {
throw new Error(`type "${data[0]}" is not valid for variant`);
}
buffer.pushVaruint32(i);
this.fields[i].type.serialize(buffer, data[1], state, allowExtensions);
}
function deserializeVariant(this: Type, buffer: SerialBuffer, state?: SerializerState, allowExtensions?: boolean): any[] {
const i = buffer.getVaruint32();
if (i >= this.fields.length) {
throw new Error(`type index ${i} is not valid for variant`);
}
const field = this.fields[i];
return [field.name, field.type.deserialize(buffer, state, allowExtensions)];
}
function serializeArray(
this: Type, buffer: SerialBuffer, data: any[], state?: SerializerState, allowExtensions?: boolean
): void {
buffer.pushVaruint32(data.length);
for (const item of data) {
this.arrayOf.serialize(buffer, item, state, false);
}
}
function deserializeArray(this: Type, buffer: SerialBuffer, state?: SerializerState, allowExtensions?: boolean): any[] {
const len = buffer.getVaruint32();
const result = [];
for (let i = 0; i < len; ++i) {
result.push(this.arrayOf.deserialize(buffer, state, false));
}
return result;
}
function serializeOptional(
this: Type, buffer: SerialBuffer, data: any, state?: SerializerState, allowExtensions?: boolean
): void {
if (data === null || data === undefined) {
buffer.push(0);
} else {
buffer.push(1);
this.optionalOf.serialize(buffer, data, state, allowExtensions);
}
}
function deserializeOptional(this: Type, buffer: SerialBuffer, state?: SerializerState, allowExtensions?: boolean): any {
if (buffer.get()) {
return this.optionalOf.deserialize(buffer, state, allowExtensions);
} else {
return null;
}
}
function serializeExtension(
this: Type, buffer: SerialBuffer, data: any, state?: SerializerState, allowExtensions?: boolean
): void {
this.extensionOf.serialize(buffer, data, state, allowExtensions);
}
function deserializeExtension(this: Type, buffer: SerialBuffer, state?: SerializerState, allowExtensions?: boolean): any {
return this.extensionOf.deserialize(buffer, state, allowExtensions);
}
function serializeObject(
this: Type, buffer: SerialBuffer, data: any, state?: SerializerState, allowExtensions?: boolean
): void {
const entries = Object.entries(data);
buffer.pushVaruint32(entries.length);
for (const [key, value] of entries) {
const keyType = this.fields[0].type;
const dataType = this.fields[1].type;
keyType.serialize(buffer, key, state, allowExtensions);
dataType.serialize(buffer, value, state, allowExtensions);
}
}
function deserializeObject(this: Type, buffer: SerialBuffer, state?: SerializerState, allowExtensions?: boolean): any {
const len = buffer.getVaruint32();
const result = {} as any;
for (let i = 0; i < len; ++i) {
const keyType = this.fields[0].type;
const dataType = this.fields[1].type;
const key = keyType.deserialize(buffer, state, allowExtensions);
(result as any)[key] = dataType.deserialize(buffer, state, allowExtensions);
}
return result;
}
function serializePair(
this: Type, buffer: SerialBuffer, data: any, state?: SerializerState, allowExtensions?: boolean
): void {
buffer.pushVaruint32(data.length);
data.forEach((item: [number, string]) => {
this.fields[0].type.serialize(buffer, item[0], state, allowExtensions);
this.fields[1].type.serialize(buffer, item[1], state, allowExtensions);
});
}
function deserializePair(this: Type, buffer: SerialBuffer, state?: SerializerState, allowExtensions?: boolean): any {
const result = [] as any;
const len = buffer.getVaruint32();
for (let i = 0; i < len; ++i) {
result.push(this.fields[0].type.deserialize(buffer, state, allowExtensions));
result.push(this.fields[1].type.deserialize(buffer, state, allowExtensions));
}
return result;
}
interface CreateTypeArgs {
name?: string;
aliasOfName?: string;
arrayOf?: Type;
optionalOf?: Type;
extensionOf?: Type;
baseName?: string;
base?: Type;
fields?: Field[];
serialize?: (buffer: SerialBuffer, data: any, state?: SerializerState, allowExtensions?: boolean) => void;
deserialize?: (buffer: SerialBuffer, state?: SerializerState, allowExtensions?: boolean) => any;
}
const createType = (attrs: CreateTypeArgs): Type => {
return {
name: '<missing name>',
aliasOfName: '',
arrayOf: null,
optionalOf: null,
extensionOf: null,
baseName: '',
base: null,
fields: [],
serialize: serializeUnknown,
deserialize: deserializeUnknown,
...attrs,
};
};
const checkRange = (orig: number, converted: number): number => {
if (Number.isNaN(+orig) || Number.isNaN(+converted) || (typeof orig !== 'number' && typeof orig !== 'string')) {
throw new Error('Expected number');
}
if (+orig !== +converted) {
throw new Error('Number is out of range');
}
return +orig;
};
/** Create the set of types built-in to the abi format */
export const createInitialTypes = (): Map<string, Type> => {
const result: Map<string, Type> = new Map(Object.entries({
bool: createType({
name: 'bool',
serialize: (buffer: SerialBuffer, data: boolean) => {
if ( !(typeof data === 'boolean' || typeof data === 'number' && ( data === 1 || data === 0))) {
throw new Error('Expected boolean or number equal to 1 or 0');
}
buffer.push(data ? 1 : 0);
},
deserialize: (buffer: SerialBuffer) => { return !!buffer.get(); },
}),
uint8: createType({
name: 'uint8',
serialize: (buffer: SerialBuffer, data: number) => { buffer.push(checkRange(data, data & 0xff)); },
deserialize: (buffer: SerialBuffer) => { return buffer.get(); },
}),
int8: createType({
name: 'int8',
serialize: (buffer: SerialBuffer, data: number) => { buffer.push(checkRange(data, data << 24 >> 24)); },
deserialize: (buffer: SerialBuffer) => { return buffer.get() << 24 >> 24; },
}),
uint16: createType({
name: 'uint16',
serialize: (buffer: SerialBuffer, data: number) => { buffer.pushUint16(checkRange(data, data & 0xffff)); },
deserialize: (buffer: SerialBuffer) => { return buffer.getUint16(); },
}),
int16: createType({
name: 'int16',
serialize: (buffer: SerialBuffer, data: number) => { buffer.pushUint16(checkRange(data, data << 16 >> 16)); },
deserialize: (buffer: SerialBuffer) => { return buffer.getUint16() << 16 >> 16; },
}),
uint32: createType({
name: 'uint32',
serialize: (buffer: SerialBuffer, data: number) => { buffer.pushUint32(checkRange(data, data >>> 0)); },
deserialize: (buffer: SerialBuffer) => { return buffer.getUint32(); },
}),
uint64: createType({
name: 'uint64',
serialize: (buffer: SerialBuffer, data: string | number) => {
buffer.pushArray(numeric.decimalToBinary(8, '' + data));
},
deserialize: (buffer: SerialBuffer) => { return numeric.binaryToDecimal(buffer.getUint8Array(8)); },
}),
int64: createType({
name: 'int64',
serialize: (buffer: SerialBuffer, data: string | number) => {
buffer.pushArray(numeric.signedDecimalToBinary(8, '' + data));
},
deserialize: (buffer: SerialBuffer) => { return numeric.signedBinaryToDecimal(buffer.getUint8Array(8)); },
}),
int32: createType({
name: 'int32',
serialize: (buffer: SerialBuffer, data: number) => { buffer.pushUint32(checkRange(data, data | 0)); },
deserialize: (buffer: SerialBuffer) => { return buffer.getUint32() | 0; },
}),
varuint32: createType({
name: 'varuint32',
serialize: (buffer: SerialBuffer, data: number) => { buffer.pushVaruint32(checkRange(data, data >>> 0)); },
deserialize: (buffer: SerialBuffer) => { return buffer.getVaruint32(); },
}),
varint32: createType({
name: 'varint32',
serialize: (buffer: SerialBuffer, data: number) => { buffer.pushVarint32(checkRange(data, data | 0)); },
deserialize: (buffer: SerialBuffer) => { return buffer.getVarint32(); },
}),
uint128: createType({
name: 'uint128',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushArray(numeric.decimalToBinary(16, '' + data)); },
deserialize: (buffer: SerialBuffer) => { return numeric.binaryToDecimal(buffer.getUint8Array(16)); },
}),
int128: createType({
name: 'int128',
serialize: (buffer: SerialBuffer, data: string) => {
buffer.pushArray(numeric.signedDecimalToBinary(16, '' + data));
},
deserialize: (buffer: SerialBuffer) => { return numeric.signedBinaryToDecimal(buffer.getUint8Array(16)); },
}),
float32: createType({
name: 'float32',
serialize: (buffer: SerialBuffer, data: number) => { buffer.pushFloat32(data); },
deserialize: (buffer: SerialBuffer) => { return buffer.getFloat32(); },
}),
float64: createType({
name: 'float64',
serialize: (buffer: SerialBuffer, data: number) => { buffer.pushFloat64(data); },
deserialize: (buffer: SerialBuffer) => { return buffer.getFloat64(); },
}),
float128: createType({
name: 'float128',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushUint8ArrayChecked(hexToUint8Array(data), 16); },
deserialize: (buffer: SerialBuffer) => { return arrayToHex(buffer.getUint8Array(16)); },
}),
bytes: createType({
name: 'bytes',
serialize: (buffer: SerialBuffer, data: string | Uint8Array | number[]) => {
if (data instanceof Uint8Array || Array.isArray(data)) {
buffer.pushBytes(data);
} else {
buffer.pushBytes(hexToUint8Array(data));
}
},
deserialize: (buffer: SerialBuffer, state?: SerializerState) => {
if (state && state.options.bytesAsUint8Array) {
return buffer.getBytes();
} else {
return arrayToHex(buffer.getBytes());
}
},
}),
string: createType({
name: 'string',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushString(data); },
deserialize: (buffer: SerialBuffer) => { return buffer.getString(); },
}),
name: createType({
name: 'name',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushName(data); },
deserialize: (buffer: SerialBuffer) => { return buffer.getName(); },
}),
time_point: createType({
name: 'time_point',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushNumberAsUint64(dateToTimePoint(data)); },
deserialize: (buffer: SerialBuffer) => { return timePointToDate(buffer.getUint64AsNumber()); },
}),
time_point_sec: createType({
name: 'time_point_sec',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushUint32(dateToTimePointSec(data)); },
deserialize: (buffer: SerialBuffer) => { return timePointSecToDate(buffer.getUint32()); },
}),
block_timestamp_type: createType({
name: 'block_timestamp_type',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushUint32(dateToBlockTimestamp(data)); },
deserialize: (buffer: SerialBuffer) => { return blockTimestampToDate(buffer.getUint32()); },
}),
symbol_code: createType({
name: 'symbol_code',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushSymbolCode(data); },
deserialize: (buffer: SerialBuffer) => { return buffer.getSymbolCode(); },
}),
symbol: createType({
name: 'symbol',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushSymbol(stringToSymbol(data)); },
deserialize: (buffer: SerialBuffer) => { return symbolToString(buffer.getSymbol()); },
}),
asset: createType({
name: 'asset',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushAsset(data); },
deserialize: (buffer: SerialBuffer) => { return buffer.getAsset(); },
}),
checksum160: createType({
name: 'checksum160',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushUint8ArrayChecked(hexToUint8Array(data), 20); },
deserialize: (buffer: SerialBuffer) => { return arrayToHex(buffer.getUint8Array(20)); },
}),
checksum256: createType({
name: 'checksum256',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushUint8ArrayChecked(hexToUint8Array(data), 32); },
deserialize: (buffer: SerialBuffer) => { return arrayToHex(buffer.getUint8Array(32)); },
}),
checksum512: createType({
name: 'checksum512',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushUint8ArrayChecked(hexToUint8Array(data), 64); },
deserialize: (buffer: SerialBuffer) => { return arrayToHex(buffer.getUint8Array(64)); },
}),
public_key: createType({
name: 'public_key',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushPublicKey(data); },
deserialize: (buffer: SerialBuffer) => { return buffer.getPublicKey(); },
}),
private_key: createType({
name: 'private_key',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushPrivateKey(data); },
deserialize: (buffer: SerialBuffer) => { return buffer.getPrivateKey(); },
}),
signature: createType({
name: 'signature',
serialize: (buffer: SerialBuffer, data: string) => { buffer.pushSignature(data); },
deserialize: (buffer: SerialBuffer) => { return buffer.getSignature(); },
}),
}));
result.set('extended_asset', createType({
name: 'extended_asset',
baseName: '',
fields: [
{ name: 'quantity', typeName: 'asset', type: result.get('asset') },
{ name: 'contract', typeName: 'name', type: result.get('name') },
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
return result;
}; // createInitialTypes()
export const createAbiTypes = (): Map<string, Type> => {
const initialTypes = createInitialTypes();
initialTypes.set('extensions_entry', createType({
name: 'extensions_entry',
baseName: '',
fields: [
{ name: 'tag', typeName: 'uint16', type: null },
{ name: 'value', typeName: 'bytes', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('type_def', createType({
name: 'type_def',
baseName: '',
fields: [
{ name: 'new_type_name', typeName: 'string', type: null },
{ name: 'type', typeName: 'string', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('field_def', createType({
name: 'field_def',
baseName: '',
fields: [
{ name: 'name', typeName: 'string', type: null },
{ name: 'type', typeName: 'string', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('struct_def', createType({
name: 'struct_def',
baseName: '',
fields: [
{ name: 'name', typeName: 'string', type: null },
{ name: 'base', typeName: 'string', type: null },
{ name: 'fields', typeName: 'field_def[]', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('action_def', createType({
name: 'action_def',
baseName: '',
fields: [
{ name: 'name', typeName: 'name', type: null },
{ name: 'type', typeName: 'string', type: null },
{ name: 'ricardian_contract', typeName: 'string', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('table_def', createType({
name: 'table_def',
baseName: '',
fields: [
{ name: 'name', typeName: 'name', type: null },
{ name: 'index_type', typeName: 'string', type: null },
{ name: 'key_names', typeName: 'string[]', type: null },
{ name: 'key_types', typeName: 'string[]', type: null },
{ name: 'type', typeName: 'string', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('clause_pair', createType({
name: 'clause_pair',
baseName: '',
fields: [
{ name: 'id', typeName: 'string', type: null },
{ name: 'body', typeName: 'string', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('error_message', createType({
name: 'error_message',
baseName: '',
fields: [
{ name: 'error_code', typeName: 'uint64', type: null },
{ name: 'error_msg', typeName: 'string', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('variant_def', createType({
name: 'variant_def',
baseName: '',
fields: [
{ name: 'name', typeName: 'string', type: null },
{ name: 'types', typeName: 'string[]', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('action_result', createType({
name: 'action_result',
baseName: '',
fields: [
{ name: 'name', typeName: 'name', type: null },
{ name: 'result_type', typeName: 'string', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('primary_key_index_def', createType({
name: 'primary_key_index_def',
baseName: '',
fields: [
{ name: 'name', typeName: 'name', type: null },
{ name: 'type', typeName: 'string', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('secondary_index_def', createType({
name: 'secondary_index_def',
baseName: '',
fields: [
{ name: 'type', typeName: 'string', type: null },
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('secondary_indices', createType({
name: 'secondary_indices',
baseName: '',
fields: [
{ name: 'name', typeName: 'name', type: null },
{ name: 'secondary_index_def', typeName: 'secondary_index_def', type: null }
],
serialize: serializeObject,
deserialize: deserializeObject,
}));
initialTypes.set('kv_table_entry_def', createType({
name: 'kv_table_entry_def',
baseName: '',
fields: [
{ name: 'type', typeName: 'string', type: null },
{ name: 'primary_index', typeName: 'primary_key_index_def', type: null },
{ name: 'secondary_indices', typeName: 'secondary_indices', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('kv_table', createType({
name: 'kv_table',
baseName: '',
fields: [
{ name: 'name', typeName: 'name', type: null },
{ name: 'kv_table_entry_def', typeName: 'kv_table_entry_def', type: null }
],
serialize: serializeObject,
deserialize: deserializeObject
}));
initialTypes.set('abi_def', createType({
name: 'abi_def',
baseName: '',
fields: [
{ name: 'version', typeName: 'string', type: null },
{ name: 'types', typeName: 'type_def[]', type: null },
{ name: 'structs', typeName: 'struct_def[]', type: null },
{ name: 'actions', typeName: 'action_def[]', type: null },
{ name: 'tables', typeName: 'table_def[]', type: null },
{ name: 'ricardian_clauses', typeName: 'clause_pair[]', type: null },
{ name: 'error_messages', typeName: 'error_message[]', type: null },
{ name: 'abi_extensions', typeName: 'extensions_entry[]', type: null },
{ name: 'variants', typeName: 'variant_def[]$', type: null },
{ name: 'action_results', typeName: 'action_result[]$', type: null },
{ name: 'kv_tables', typeName: 'kv_table$', type: null },
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
return initialTypes;
};
export const createTransactionExtensionTypes = (): Map<string, Type> => {
const initialTypes = createInitialTypes();
initialTypes.set('resource_payer', createType({
name: 'resource_payer',
baseName: '',
fields: [
{ name: 'payer', typeName: 'name', type: null },
{ name: 'max_net_bytes', typeName: 'uint64', type: null },
{ name: 'max_cpu_us', typeName: 'uint64', type: null },
{ name: 'max_memory_bytes', typeName: 'uint64', type: null },
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
return initialTypes;
};
export const createTransactionTypes = (): Map<string, Type> => {
const initialTypes = createInitialTypes();
initialTypes.set('permission_level', createType({
name: 'permission_level',
baseName: '',
fields: [
{ name: 'actor', typeName: 'name', type: null },
{ name: 'permission', typeName: 'name', type: null },
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('action', createType({
name: 'action',
baseName: '',
fields: [
{ name: 'account', typeName: 'name', type: null },
{ name: 'name', typeName: 'name', type: null },
{ name: 'authorization', typeName: 'permission_level[]', type: null },
{ name: 'data', typeName: 'bytes', type: null },
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('extension', createType({
name: 'extension',
baseName: '',
fields: [
{ name: 'type', typeName: 'uint16', type: null },
{ name: 'data', typeName: 'bytes', type: null },
],
serialize: serializePair,
deserialize: deserializePair,
}));
initialTypes.set('transaction_header', createType({
name: 'transaction_header',
baseName: '',
fields: [
{ name: 'expiration', typeName: 'time_point_sec', type: null },
{ name: 'ref_block_num', typeName: 'uint16', type: null },
{ name: 'ref_block_prefix', typeName: 'uint32', type: null },
{ name: 'max_net_usage_words', typeName: 'varuint32', type: null },
{ name: 'max_cpu_usage_ms', typeName: 'uint8', type: null },
{ name: 'delay_sec', typeName: 'varuint32', type: null },
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
initialTypes.set('transaction', createType({
name: 'transaction',
baseName: 'transaction_header',
fields: [
{ name: 'context_free_actions', typeName: 'action[]', type: null },
{ name: 'actions', typeName: 'action[]', type: null },
{ name: 'transaction_extensions', typeName: 'extension', type: null }
],
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
return initialTypes;
};
/** Get type from `types` */
export const getType = (types: Map<string, Type>, name: string): Type => {
const type = types.get(name);
if (type && type.aliasOfName) {
return getType(types, type.aliasOfName);
}
if (type) {
return type;
}
if (name.endsWith('[]')) {
return createType({
name,
arrayOf: getType(types, name.substr(0, name.length - 2)),
serialize: serializeArray,
deserialize: deserializeArray,
});
}
if (name.endsWith('?')) {
return createType({
name,
optionalOf: getType(types, name.substr(0, name.length - 1)),
serialize: serializeOptional,
deserialize: deserializeOptional,
});
}
if (name.endsWith('$')) {
return createType({
name,
extensionOf: getType(types, name.substr(0, name.length - 1)),
serialize: serializeExtension,
deserialize: deserializeExtension,
});
}
throw new Error('Unknown type: ' + name);
};
/**
* Get types from abi
*
* @param initialTypes Set of types to build on.
* In most cases, it's best to fill this from a fresh call to `getTypesFromAbi()`.
*/
export const getTypesFromAbi = (initialTypes: Map<string, Type>, abi?: Abi): Map<string, Type> => {
const types = new Map(initialTypes);
if (abi && abi.types) {
for (const { new_type_name, type } of abi.types) {
types.set(new_type_name,
createType({ name: new_type_name, aliasOfName: type }));
}
}
if (abi && abi.structs) {
for (const { name, base, fields } of abi.structs) {
types.set(name, createType({
name,
baseName: base,
fields: fields.map(({ name: n, type }) => ({ name: n, typeName: type, type: null })),
serialize: serializeStruct,
deserialize: deserializeStruct,
}));
}
}
if (abi && abi.variants) {
for (const { name, types: t } of abi.variants) {
types.set(name, createType({
name,
fields: t.map((s) => ({ name: s, typeName: s, type: null })),
serialize: serializeVariant,
deserialize: deserializeVariant,
}));
}
}
for (const [name, type] of types) {
if (type.baseName) {
type.base = getType(types, type.baseName);
}
for (const field of type.fields) {
field.type = getType(types, field.typeName);
}
}
return types;
}; // getTypesFromAbi
const reverseHex = (h: string): string => {
return h.substr(6, 2) + h.substr(4, 2) + h.substr(2, 2) + h.substr(0, 2);
};
/** TAPoS: Return transaction fields which reference `refBlock` and expire `expireSeconds` after `timestamp` */
export const transactionHeader = (refBlock: BlockTaposInfo, expireSeconds: number): TransactionHeader => {
const timestamp = refBlock.header ? refBlock.header.timestamp : refBlock.timestamp;
const prefix = parseInt(reverseHex(refBlock.id.substr(16, 8)), 16);
return {
expiration: timePointSecToDate(dateToTimePointSec(timestamp) + expireSeconds),
ref_block_num: refBlock.block_num & 0xffff,
ref_block_prefix: prefix,
};
};
/** Convert action data to serialized form (hex) */
export const serializeActionData = (
contract: Contract, account: string, name: string, data: any, textEncoder: TextEncoder, textDecoder: TextDecoder
): string => {
const action = contract.actions.get(name);
if (!action) {
throw new Error(`Unknown action ${name} in contract ${account}`);
}
const buffer = new SerialBuffer({ textEncoder, textDecoder });
action.serialize(buffer, data);
return arrayToHex(buffer.asUint8Array());
};
/** Return action in serialized form */
export const serializeAction = (
contract: Contract, account: string, name: string, authorization: Authorization[],
data: any, textEncoder: TextEncoder, textDecoder: TextDecoder
): SerializedAction => {
return {
account,
name,
authorization,
data: serializeActionData(contract, account, name, data, textEncoder, textDecoder),
};
};
/** Deserialize action data. If `data` is a `string`, then it's assumed to be in hex. */
export const deserializeActionData = (
contract: Contract, account: string, name: string, data: string | Uint8Array | number[],
textEncoder: TextEncoder, textDecoder: TextDecoder
): any => {
const action = contract.actions.get(name);
if (typeof data === 'string') {
data = hexToUint8Array(data);
}
if (!action) {
throw new Error(`Unknown action ${name} in contract ${account}`);
}
const buffer = new SerialBuffer({ textDecoder, textEncoder });
buffer.pushArray(data);
return action.deserialize(buffer);
};
/** Deserialize action. If `data` is a `string`, then it's assumed to be in hex. */
export const deserializeAction = (
contract: Contract, account: string, name: string, authorization: Authorization[],
data: string | Uint8Array | number[], textEncoder: TextEncoder, textDecoder: TextDecoder
): Action => {
return {
account,
name,
authorization,
data: deserializeActionData(contract, account, name, data, textEncoder, textDecoder),
};
};
export const serializeAnyvar = (buffer: SerialBuffer, anyvar: Anyvar): void => {
let def: AnyvarDef;
let value: any;
if (anyvar === null) {
[def, value] = [anyvarDefs.null_t, anyvar];
} else if (typeof anyvar === 'string') {
[def, value] = [anyvarDefs.string, anyvar];
} else if (typeof anyvar === 'number') {
[def, value] = [anyvarDefs.int32, anyvar];
} else if (anyvar instanceof Uint8Array) {
[def, value] = [anyvarDefs.bytes, anyvar];
} else if (Array.isArray(anyvar)) {
[def, value] = [anyvarDefs.any_array, anyvar];
} else if (Object.keys(anyvar).length === 2 && anyvar.hasOwnProperty('type') && anyvar.hasOwnProperty('value')) {
[def, value] = [(anyvarDefs as any)[(anyvar as any).type] as AnyvarDef, (anyvar as any).value];
} else {
[def, value] = [anyvarDefs.any_object, anyvar];
}
buffer.pushVaruint32(def.index);
def.type.serialize(buffer, value);
};
export const deserializeAnyvar = (buffer: SerialBuffer, state?: SerializerState): any => {
const defIndex = buffer.getVaruint32();
if (defIndex >= anyvarDefsByIndex.length) {
throw new Error('Tried to deserialize unknown anyvar type');
}
const def = anyvarDefsByIndex[defIndex];
const value = def.type.deserialize(buffer, state);
if (state && (state.options as any).useShortForm || def.useShortForm) {
return value;
} else {
return { type: def.type.name, value };
}
};
export const deserializeAnyvarShort = (buffer: SerialBuffer): any => {
return deserializeAnyvar(buffer, new SerializerState({ useShortForm: true } as any));
};
export const serializeAnyObject = (buffer: SerialBuffer, obj: any): void => {
const entries = Object.entries(obj);
buffer.pushVaruint32(entries.length);
for (const [key, value] of entries) {
buffer.pushString(key);
serializeAnyvar(buffer, value as Anyvar);
}
};
export const deserializeAnyObject = (buffer: SerialBuffer, state?: SerializerState): any => {
const len = buffer.getVaruint32();
const result = {};
for (let i = 0; i < len; ++i) {
let key = buffer.getString();
if (key in result) {
let j = 1;
while (key + '_' + j in result) {
++j;
}
key = key + '_' + j;
}
(result as any)[key] = deserializeAnyvar(buffer, state);
}
return result;
};
export const serializeAnyArray = (buffer: SerialBuffer, arr: Anyvar[]): void => {
buffer.pushVaruint32(arr.length);
for (const x of arr) {
serializeAnyvar(buffer, x);
}
};
export const deserializeAnyArray = (buffer: SerialBuffer, state?: SerializerState): any[] => {
const len = buffer.getVaruint32();
const result = [];
for (let i = 0; i < len; ++i) {
result.push(deserializeAnyvar(buffer, state));
}
return result;
};
const addAdditionalTypes = (): Map<string, Type> => {
const initialTypes = createInitialTypes();
initialTypes.set('null_t', createType({
name: 'null_t',
serialize: (buffer: SerialBuffer, anyvar: Anyvar) => {},
deserialize: (buffer: SerialBuffer, state?: SerializerState) => {}
}));
initialTypes.set('any_object', createType({
name: 'any_object',
serialize: serializeAnyObject,
deserialize: deserializeAnyObject
}));
initialTypes.set('any_array', createType({
name: 'any_array',
serialize: serializeAnyArray,
deserialize: deserializeAnyArray
}));
return initialTypes;
};
const additionalTypes = addAdditionalTypes();
const anyvarDefs = {
null_t: { index: 0, useShortForm: true, type: additionalTypes.get('null_t') },
int64: { index: 1, useShortForm: false, type: additionalTypes.get('int64') },
uint64: { index: 2, useShortForm: false, type: additionalTypes.get('uint64') },
int32: { index: 3, useShortForm: true, type: additionalTypes.get('int32') },
uint32: { index: 4, useShortForm: false, type: additionalTypes.get('uint32') },
int16: { index: 5, useShortForm: false, type: additionalTypes.get('int16') },
uint16: { index: 6, useShortForm: false, type: additionalTypes.get('uint16') },
int8: { index: 7, useShortForm: false, type: additionalTypes.get('int8') },
uint8: { index: 8, useShortForm: false, type: additionalTypes.get('uint8') },
time_point: { index: 9, useShortForm: false, type: additionalTypes.get('time_point') },
checksum256: { index: 10, useShortForm: false, type: additionalTypes.get('checksum256') },
float64: { index: 11, useShortForm: false, type: additionalTypes.get('float64') },
string: { index: 12, useShortForm: true, type: additionalTypes.get('string') },
any_object: { index: 13, useShortForm: true, type: additionalTypes.get('any_object') },
any_array: { index: 14, useShortForm: true, type: additionalTypes.get('any_array') },
bytes: { index: 15, useShortForm: false, type: additionalTypes.get('bytes') },
symbol: { index: 16, useShortForm: false, type: additionalTypes.get('symbol') },
symbol_code: { index: 17, useShortForm: false, type: additionalTypes.get('symbol_code') },
asset: { index: 18, useShortForm: false, type: additionalTypes.get('asset') },
};
const anyvarDefsByIndex = [
anyvarDefs.null_t,
anyvarDefs.int64,
anyvarDefs.uint64,
anyvarDefs.int32,
anyvarDefs.uint32,
anyvarDefs.int16,
anyvarDefs.uint16,
anyvarDefs.int8,
anyvarDefs.uint8,
anyvarDefs.time_point,
anyvarDefs.checksum256,
anyvarDefs.float64,
anyvarDefs.string,
anyvarDefs.any_object,
anyvarDefs.any_array,
anyvarDefs.bytes,
anyvarDefs.symbol,
anyvarDefs.symbol_code,
anyvarDefs.asset,
];
export const serializeQuery = (buffer: SerialBuffer, query: Query): void => {
let method: string;
let arg: Anyvar;
let filter: Query[];
if (typeof query === 'string') {
method = query;
} else if (Array.isArray(query) && query.length === 2) {
[method, filter] = query;
} else if (Array.isArray(query) && query.length === 3) {
[method, arg, filter] = query;
} else {
[method, arg, filter] = [query.method, query.arg, query.filter];
}
buffer.pushString(method);
if (arg === undefined) {
buffer.push(0);
} else {
buffer.push(1);
serializeAnyvar(buffer, arg);
}
if (filter === undefined) {
buffer.push(0);
} else {
buffer.pushVaruint32(filter.length);
for (const q of filter) {
serializeQuery(buffer, q);
}
}
};
/**
* @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