├── test ├── _node.js ├── tsconfig.json ├── _browser.js ├── _karma.js ├── misc.ts ├── rc.ts ├── serializers.ts ├── broadcast.ts ├── client.ts ├── common.ts ├── serializer-tests.json ├── asset.ts ├── blockchain.ts ├── crypto.ts ├── database.ts ├── _karma-sauce.js └── operations.ts ├── docs ├── README.md ├── assets │ └── images │ │ ├── icons.png │ │ ├── widgets.png │ │ ├── icons@2x.png │ │ └── widgets@2x.png ├── interfaces │ ├── pool.html │ ├── beneficiaryroute.html │ └── resource.html └── enums │ └── blockchainmode.html ├── examples ├── comment-feed │ ├── contents │ │ ├── app.styl │ │ ├── index.json │ │ ├── styles │ │ │ ├── utils.styl │ │ │ ├── spinner.styl │ │ │ ├── typography.styl │ │ │ └── main.styl │ │ ├── app.ts │ │ └── scripts │ │ │ └── main.ts │ ├── bin │ │ └── build.ts │ ├── tsconfig.json │ ├── templates │ │ ├── index.html │ │ └── layout.html │ ├── package.json │ ├── README.md │ └── config.json └── vote-bot │ ├── package.json │ ├── README.md │ └── bot.js ├── dist └── dsteem.js.gz ├── .gitignore ├── src ├── version.ts ├── steem │ ├── rc.ts │ ├── transaction.ts │ ├── block.ts │ ├── comment.ts │ ├── account.ts │ ├── asset.ts │ └── misc.ts ├── index-node.ts ├── index.ts ├── index-browser.ts ├── helpers │ ├── rc.ts │ ├── blockchain.ts │ └── database.ts ├── utils.ts └── client.ts ├── .travis.yml ├── .babelrc ├── tsconfig.json ├── tslint.json ├── LICENSE ├── package.json ├── .circleci └── config.yml ├── Makefile └── README.md /test/_node.js: -------------------------------------------------------------------------------- 1 | require('../src/index-node') 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Served at 2 | -------------------------------------------------------------------------------- /examples/comment-feed/contents/app.styl: -------------------------------------------------------------------------------- 1 | @import './styles/main.styl' 2 | -------------------------------------------------------------------------------- /examples/comment-feed/contents/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "index.html" 3 | } -------------------------------------------------------------------------------- /dist/dsteem.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorykop/dsteem/HEAD/dist/dsteem.js.gz -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | coverage/ 4 | .nyc_output/ 5 | .testnetrc 6 | .idea 7 | .vscode 8 | -------------------------------------------------------------------------------- /docs/assets/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorykop/dsteem/HEAD/docs/assets/images/icons.png -------------------------------------------------------------------------------- /docs/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorykop/dsteem/HEAD/docs/assets/images/widgets.png -------------------------------------------------------------------------------- /docs/assets/images/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorykop/dsteem/HEAD/docs/assets/images/icons@2x.png -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | // overwritten by buildscript 2 | export default require('./../package.json').version as string 3 | -------------------------------------------------------------------------------- /docs/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorykop/dsteem/HEAD/docs/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /examples/vote-bot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "dsteem": "^0.8.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "9" 5 | - "10" 6 | after_success: 7 | - "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls" 8 | -------------------------------------------------------------------------------- /examples/vote-bot/README.md: -------------------------------------------------------------------------------- 1 | 2 | vote-vot 3 | ======== 4 | 5 | Simple steemit copycat votebot. 6 | 7 | Usage 8 | ----- 9 | 10 | ``` 11 | FOLLOW_USER=epic-curator BOT_USER=sheepbot POSTING_KEY=5lalalalalalallalala node bot.js 12 | ``` 13 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "browsers": [ 8 | ">1%", 9 | "not op_mini all" 10 | ] 11 | } 12 | } 13 | ] 14 | ] 15 | } -------------------------------------------------------------------------------- /examples/comment-feed/contents/styles/utils.styl: -------------------------------------------------------------------------------- 1 | 2 | .flex-center 3 | display: flex 4 | flex-direction: column 5 | align-items: center 6 | justify-content: center 7 | 8 | .fixed-overlay 9 | top: 0 10 | left: 0 11 | position: fixed 12 | width: 100% 13 | height: 100% 14 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2015", "esnext.asynciterable", "dom"], 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "target": "es5", 7 | "types" : ["node", "verror"] 8 | }, 9 | "include": [ 10 | "*.ts" 11 | ] 12 | } -------------------------------------------------------------------------------- /examples/comment-feed/bin/build.ts: -------------------------------------------------------------------------------- 1 | import {exec, exit} from 'shelljs' 2 | 3 | exec('wintersmith build -X') || exit(1) 4 | 5 | exec('uglifyjs build/app.js' + 6 | ' --source-map "content=inline,url=app.js.map,filename=build/app.js.map"' + 7 | ' --compress "dead_code,collapse_vars,reduce_vars,keep_infinity,drop_console,passes=2"' + 8 | ' -o build/app.js') 9 | -------------------------------------------------------------------------------- /test/_browser.js: -------------------------------------------------------------------------------- 1 | global['isBrowser'] = true 2 | require('../src/index-browser') 3 | require('./asset.ts') 4 | require('./blockchain.ts') 5 | require('./broadcast.ts') 6 | require('./client.ts') 7 | require('./crypto.ts') 8 | require('./database.ts') 9 | require('./rc.ts') 10 | require('./misc.ts') 11 | require('./operations.ts') 12 | require('./serializers.ts') 13 | -------------------------------------------------------------------------------- /examples/comment-feed/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "es2015", 6 | "esnext.asynciterable" 7 | ], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "target": "es5", 11 | "allowJs": true 12 | }, 13 | "exclude": [ 14 | "node_modules" 15 | ], 16 | "include": [ 17 | "contents/**/*.ts" 18 | ] 19 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "lib": ["es2015", "esnext.asynciterable"], 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitThis": true, 8 | "outDir": "lib", 9 | "strictNullChecks": true, 10 | "target": "es6", 11 | "types" : ["node", "verror"] 12 | }, 13 | "include": [ 14 | "src/**/*.ts" 15 | ] 16 | } -------------------------------------------------------------------------------- /examples/comment-feed/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | 3 | {% block body %} 4 |
5 |

6 | Hello there sailor, looks like your JavaScript is off. No es bueno! 7 |

8 |
9 |
10 | 11 |

Loading...

12 |
13 |
14 |
15 | dsteem – comment feed example 16 | Pause 17 |
18 | {% endblock %} -------------------------------------------------------------------------------- /examples/comment-feed/contents/app.ts: -------------------------------------------------------------------------------- 1 | require('core-js/es6/promise') 2 | 3 | import main from './scripts/main' 4 | 5 | window.addEventListener('DOMContentLoaded', () => { 6 | main().then(() => { 7 | document.documentElement.classList.remove('loading') 8 | }).catch((error: Error) => { 9 | console.error('Unable to start application', error) 10 | document.documentElement.classList.add('error') 11 | const loading = document.getElementById('loading') 12 | loading.dataset['error'] = error.message || String(error) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /test/_karma.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | frameworks: ['browserify', 'mocha'], 4 | files: [ 5 | '_browser.js' 6 | ], 7 | preprocessors: { 8 | '_browser.js': [ 'browserify' ] 9 | }, 10 | browserNoActivityTimeout: 1000 * 60 * 5, 11 | browserify: { 12 | debug: true, 13 | plugin: [ ['tsify', {project: 'test/tsconfig.json'} ] ] 14 | }, 15 | reporters: ['mocha'], 16 | port: 9876, 17 | colors: true, 18 | logLevel: config.LOG_INFO, 19 | autoWatch: true, 20 | singleRun: false, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "rules": { 5 | "ban-types": false, 6 | "indent": [true, "spaces", 4], 7 | "interface-name": [true, "never-prefix"], 8 | "max-classes-per-file": false, 9 | "no-bitwise": false, 10 | "no-var-requires": false, 11 | "one-variable-per-declaration": false, 12 | "quotemark": [true, "single", "avoid-escape"], 13 | "semicolon": [true, "never"], 14 | "trailing-comma": [true, "never"], 15 | "triple-equals": [true, "allow-undefined-check"], 16 | "variable-name": false 17 | } 18 | } -------------------------------------------------------------------------------- /examples/comment-feed/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "preview": "wintersmith preview", 5 | "build": "ts-node bin/build.ts" 6 | }, 7 | "dependencies": { 8 | "@types/node": "^8.0.28", 9 | "@types/shelljs": "^0.7.4", 10 | "core-js": "^2.5.1", 11 | "dsteem": "^0.8.1", 12 | "remove-markdown": "^0.2.2", 13 | "shelljs": "^0.7.8", 14 | "ts-node": "^3.3.0", 15 | "tsify": "^3.0.3", 16 | "typescript": "^2.5.2", 17 | "uglify-js": "^3.1.0", 18 | "wintersmith": "^2.4.1", 19 | "wintersmith-browserify": "^1.3.0", 20 | "wintersmith-livereload": "^1.0.0", 21 | "wintersmith-nunjucks": "^2.0.0", 22 | "wintersmith-stylus": "^0.5.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/comment-feed/README.md: -------------------------------------------------------------------------------- 1 | 2 | dsteem comment feed example 3 | =========================== 4 | 5 | Running on: 6 | 7 | 8 | Developing 9 | ---------- 10 | 11 | Download and install [node.js](https://nodejs.org). 12 | 13 | Download or clone this repository then run: 14 | 15 | ``` 16 | npm install 17 | ``` 18 | 19 | Start the preview server by running: 20 | 21 | ``` 22 | npm run preview 23 | ``` 24 | 25 | Now a local version is running on http://localhost:8080 26 | 27 | 28 | Deploying 29 | --------- 30 | 31 | ``` 32 | npm run build 33 | ``` 34 | 35 | Static output will be in the `build/` folder ready to be copied to a webserver. 36 | 37 | --- 38 | 39 | 40 | Built with [Wintersmith](https://github.com/jnordberg/wintersmith). 41 | -------------------------------------------------------------------------------- /examples/comment-feed/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "locals": { 3 | "name": "comments.steem.vc", 4 | "description": "Watch all comments on the steem blockchain in real time!", 5 | "author": "Johan Nordberg", 6 | "version": "1.0.1" 7 | }, 8 | "ignore": [ 9 | "**/.DS_Store", 10 | "styles/**/*", 11 | "scripts/**/*" 12 | ], 13 | "browserify": { 14 | "debug": true, 15 | "watchify": false, 16 | "extensions": [".ts"], 17 | "plugins": ["tsify"], 18 | "staticLibs": ["dsteem"], 19 | "staticLibsBundle": true 20 | }, 21 | "stylus": { 22 | "include css": true, 23 | "dependencies": ["nib"] 24 | }, 25 | "plugins": [ 26 | "wintersmith-browserify", 27 | "wintersmith-livereload", 28 | "wintersmith-nunjucks", 29 | "wintersmith-stylus" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /examples/comment-feed/contents/styles/spinner.styl: -------------------------------------------------------------------------------- 1 | 2 | // Loading spinner based on Harold Soto's "Wave" 3 | // https://codepen.io/bernethe/pen/dorozd 4 | 5 | speed = 0.8s 6 | color = theme-color 7 | 8 | @keyframes spinner-before 9 | from 10 | opacity: 1 11 | transform: scale(1.0, 1.0) 12 | to 13 | opacity: 0 14 | transform: scale(1.5, 1.5) 15 | 16 | @keyframes spinner-after 17 | from 18 | opacity: 0 19 | transform: scale(0.5, 0.5) 20 | to 21 | transform: scale(1.0, 1.0) 22 | opacity: 1 23 | 24 | .spinner 25 | border-radius: 50% 26 | position: relative 27 | opacity: 1 28 | 29 | &:before, &:after 30 | content:'' 31 | border: 1px color solid 32 | border-radius: 50% 33 | width: 100% 34 | height: 100% 35 | position: absolute 36 | left: 0 37 | 38 | &:before 39 | transform: scale(1,1) 40 | opacity: 1 41 | animation: spinner-before speed infinite linear 42 | 43 | &:after 44 | transform: scale(0,0) 45 | opacity: 0 46 | animation: spinner-after speed infinite linear 47 | 48 | 49 | -------------------------------------------------------------------------------- /examples/comment-feed/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ name }} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | {{ browserifyLibs }}{{ livereloadScript -}} 16 | 17 | 18 | 19 | 20 | {%- block body %}{% endblock -%} 21 | 22 | 23 | 30 | -------------------------------------------------------------------------------- /examples/comment-feed/contents/styles/typography.styl: -------------------------------------------------------------------------------- 1 | 2 | html 3 | font-size: 17px 4 | line-height: 26px 5 | 6 | body, .p 7 | font-size: 1em 8 | line-height: 1.52941176em 9 | 10 | h1, .h1 11 | font-size: 2.82352941em 12 | line-height: 1.08333333em 13 | margin-top: 0.54166667em 14 | margin-bottom: 0.54166667em 15 | 16 | h2, .h2 17 | font-size: 2em 18 | line-height: 1.52941176em 19 | margin-top: 0.76470588em 20 | margin-bottom: 0.76470588em 21 | 22 | h3, .h3 23 | font-size: 1.41176471em 24 | line-height: 2.16666666em 25 | margin-top: 1.08333333em 26 | margin-bottom: 0em 27 | 28 | h4, .h4 29 | font-size: 1em 30 | line-height: 3.05882352em 31 | margin-top: 1.52941176em 32 | margin-bottom: 0em 33 | 34 | h5, .h5 35 | font-size: 1em 36 | line-height: 1.52941176em 37 | margin-top: 1.52941176em 38 | margin-bottom: 0em 39 | 40 | p, ul, ol, pre, table, blockquote 41 | margin-top: 0em 42 | margin-bottom: 1.52941176em 43 | 44 | ul ul, ol ol, ul ol, ol ul 45 | margin-top: 0em 46 | margin-bottom: 0em 47 | 48 | hr, .hr 49 | border: 1px solid 50 | margin: -1px 0 51 | 52 | a, b, i, strong, em, small, code 53 | line-height: 0 54 | 55 | sub, sup 56 | line-height: 0 57 | position: relative 58 | vertical-align: baseline 59 | 60 | sup 61 | top: -0.5em 62 | 63 | sub 64 | bottom: -0.25em 65 | 66 | ul li 67 | list-style: circle 68 | 69 | ol li 70 | list-style: decimal 71 | 72 | ol li, ul li 73 | margin-left: 2.611em 74 | -------------------------------------------------------------------------------- /src/steem/rc.ts: -------------------------------------------------------------------------------- 1 | import { SMTAsset } from './asset' 2 | import { Bignum } from './misc' 3 | 4 | export interface RCParams { 5 | resource_history_bytes: Resource, 6 | resource_new_accounts: Resource, 7 | resource_market_bytes: Resource, 8 | resource_state_bytes: Resource, 9 | resource_execution_time: Resource, 10 | } 11 | export interface Resource { 12 | resource_dynamics_params: DynamicParam, 13 | price_curve_params: PriceCurveParam 14 | } 15 | export interface DynamicParam { 16 | resource_unit: number, 17 | budget_per_time_unit: number, 18 | pool_eq: Bignum, 19 | max_pool_size: Bignum, 20 | decay_params: { 21 | decay_per_time_unit: Bignum, 22 | decay_per_time_unit_denom_shift: number 23 | }, 24 | min_decay: number 25 | } 26 | export interface PriceCurveParam { 27 | coeff_a: Bignum, 28 | coeff_b: Bignum, 29 | shift: number 30 | } 31 | export interface RCPool { 32 | resource_history_bytes: Pool, 33 | resource_new_accounts: Pool, 34 | resource_market_bytes: Pool, 35 | resource_state_bytes: Pool, 36 | resource_execution_time: Pool 37 | } 38 | export interface Pool { 39 | pool: Bignum 40 | } 41 | export interface RCAccount { 42 | account: string, 43 | rc_manabar: { 44 | current_mana: Bignum, 45 | last_update_time: number 46 | }, 47 | max_rc_creation_adjustment: SMTAsset | string, 48 | max_rc: Bignum 49 | } 50 | 51 | export interface Manabar { 52 | current_mana: number, 53 | max_mana: number, 54 | percentage: number 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistribution of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistribution in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 26 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 27 | OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | You acknowledge that this software is not designed, licensed or intended for use 30 | in the design, construction, operation or maintenance of any military facility. 31 | -------------------------------------------------------------------------------- /test/misc.ts: -------------------------------------------------------------------------------- 1 | import 'mocha' 2 | import * as assert from 'assert' 3 | import * as stream from 'stream' 4 | import {VError} from 'verror' 5 | 6 | import {utils} from './../src' 7 | 8 | describe('misc', function() { 9 | 10 | describe('iteratorStream', function() { 11 | 12 | async function *counter(to: number) { 13 | for (var i = 0; i < to; i++) { 14 | yield {i} 15 | } 16 | } 17 | 18 | async function *errorCounter(to: number, errorAt: number) { 19 | for (var i = 0; i < to; i++) { 20 | yield {i} 21 | if (errorAt === i) { 22 | throw new Error('Oh noes') 23 | } 24 | } 25 | } 26 | 27 | it('should handle backpressure', async function() { 28 | this.slow(500) 29 | await new Promise((resolve, reject) => { 30 | const s1 = new stream.PassThrough({highWaterMark: 10, objectMode: true}) 31 | const s2 = utils.iteratorStream(counter(100)) 32 | s2.pipe(s1) 33 | setTimeout(() => { 34 | let c = 0 35 | s1.on('data', (d: any) => { 36 | c = d.i 37 | }) 38 | s1.on('end', () => { 39 | assert.equal(c, 99) 40 | resolve() 41 | }) 42 | }, 50) 43 | }) 44 | }) 45 | 46 | it('should handle errors', async function() { 47 | await new Promise((resolve) => { 48 | const s = utils.iteratorStream(errorCounter(10, 2)) 49 | let last = 0 50 | let sawError = false 51 | s.on('data', (d) => { 52 | last = d.i 53 | }) 54 | s.on('error', (error) => { 55 | assert.equal(last, 2) 56 | sawError = true 57 | }) 58 | s.on('end', () => { 59 | assert(sawError) 60 | resolve() 61 | }) 62 | }) 63 | }) 64 | 65 | }) 66 | 67 | }) 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsteem", 3 | "version": "0.11.3", 4 | "description": "Steem blockchain RPC client library", 5 | "author": "Johan Nordberg", 6 | "license": "BSD-3-Clause", 7 | "main": "./lib/index-node", 8 | "typings": "./lib/index", 9 | "browser": { 10 | "./lib/index-node": "./dist/dsteem.js", 11 | "./src/index-node": "./dist/dsteem.js", 12 | "./src/version": "./lib/version.js" 13 | }, 14 | "files": [ 15 | "dist/*", 16 | "lib/*" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/jnordberg/dsteem" 21 | }, 22 | "scripts": { 23 | "test": "make ci-test", 24 | "prepublishOnly": "make all" 25 | }, 26 | "keywords": [ 27 | "blockchain", 28 | "client", 29 | "rpc", 30 | "steem", 31 | "steemit" 32 | ], 33 | "dependencies": { 34 | "bs58": "^4.0.1", 35 | "bytebuffer": "^5.0.1", 36 | "core-js": "^2.5.0", 37 | "node-fetch": "^2.3.0", 38 | "secp256k1": "^3.5.2", 39 | "verror": "^1.10.0", 40 | "whatwg-fetch": "^3.0.0" 41 | }, 42 | "devDependencies": { 43 | "@types/bytebuffer": "^5.0.33", 44 | "@types/mocha": "^5.2.5", 45 | "@types/node": "^10.12.9", 46 | "@types/verror": "^1.10.3", 47 | "@babel/core": "^7.1.6", 48 | "@babel/preset-env": "^7.1.6", 49 | "babelify": "^10.0.0", 50 | "browserify": "^16.2.3", 51 | "coveralls": "^3.0.2", 52 | "derequire": "^2.0.6", 53 | "dts-generator": "^2.1.0", 54 | "encoding": "^0.1.12", 55 | "karma": "^3.1.1", 56 | "karma-browserify": "^5.1.1", 57 | "karma-mocha": "^1.3.0", 58 | "karma-mocha-reporter": "^2.2.5", 59 | "karma-sauce-launcher": "^1.2.0", 60 | "lorem-ipsum": "^1.0.6", 61 | "mocha": "^5.0.0", 62 | "nyc": "^13.1.0", 63 | "regenerator-runtime": "^0.13.1", 64 | "ts-node": "^7.0.1", 65 | "tsify": "^4.0.0", 66 | "tslint": "^5.11.0", 67 | "typedoc": "^0.13.0", 68 | "typescript": "^3.1.6", 69 | "uglify-js": "^3.4.9", 70 | "watchify": "^3.11.0" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/index-node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file dsteem entry point for node.js. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import 'core-js/modules/es7.symbol.async-iterator' 37 | global['fetch'] = require('node-fetch') // tslint:disable-line:no-string-literal 38 | 39 | export * from './index' 40 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | node8: 4 | docker: 5 | - image: node:8-alpine 6 | steps: 7 | - run: apk add --no-cache make bash ca-certificates sed 8 | - checkout 9 | - restore_cache: 10 | keys: 11 | - node8-dependencies-{{ checksum "yarn.lock" }} 12 | - node8-dependencies- 13 | - run: yarn install --frozen-lockfile 14 | - save_cache: 15 | paths: 16 | - node_modules 17 | key: node8-dependencies-{{ checksum "yarn.lock" }} 18 | - run: yarn test 19 | - store_test_results: 20 | path: reports 21 | - store_artifacts: 22 | path: reports 23 | destination: reports 24 | node9: 25 | docker: 26 | - image: node:9-alpine 27 | steps: 28 | - run: apk add --no-cache make bash ca-certificates sed 29 | - checkout 30 | - restore_cache: 31 | keys: 32 | - node9-dependencies-{{ checksum "yarn.lock" }} 33 | - node9-dependencies- 34 | - run: yarn install --frozen-lockfile 35 | - save_cache: 36 | paths: 37 | - node_modules 38 | key: node9-dependencies-{{ checksum "yarn.lock" }} 39 | - run: yarn test 40 | - store_test_results: 41 | path: reports 42 | - store_artifacts: 43 | path: reports 44 | destination: reports 45 | node10: 46 | docker: 47 | - image: node:10-alpine 48 | steps: 49 | - run: apk add --no-cache make bash ca-certificates sed 50 | - checkout 51 | - restore_cache: 52 | keys: 53 | - node10-dependencies-{{ checksum "yarn.lock" }} 54 | - node10-dependencies- 55 | - run: yarn install --frozen-lockfile 56 | - save_cache: 57 | paths: 58 | - node_modules 59 | key: node10-dependencies-{{ checksum "yarn.lock" }} 60 | - run: yarn test 61 | - store_test_results: 62 | path: reports 63 | - store_artifacts: 64 | path: reports 65 | destination: reports 66 | workflows: 67 | version: 2 68 | build: 69 | jobs: 70 | - node8 71 | - node9 72 | - node10 73 | -------------------------------------------------------------------------------- /test/rc.ts: -------------------------------------------------------------------------------- 1 | import 'mocha' 2 | import * as assert from 'assert' 3 | 4 | import { Client, Asset, Transaction, PrivateKey } from './../src' 5 | import { getTestnetAccounts, randomString, agent, TEST_NODE } from './common' 6 | 7 | describe('rc_api', function () { 8 | this.slow(500) 9 | this.timeout(20 * 1000) 10 | 11 | const client = Client.testnet({ agent }) 12 | let serverConfig: { [key: string]: boolean | string | number } 13 | const liveClient = new Client(TEST_NODE, { agent }) 14 | 15 | let acc: { username: string, posting: string, active: string } 16 | /*before(async function () { 17 | [acc] = await getTestnetAccounts() 18 | })*/ 19 | 20 | // _calculateManabar max_mana: number, { current_mana, last_update_time } 21 | 22 | it('calculateVPMana', function() { 23 | let account: any = { 24 | name: 'therealwolf', 25 | voting_manabar: { 26 | current_mana: 130168665536029, 27 | last_update_time: Date.now() / 1000 28 | }, 29 | vesting_shares: '80241942 VESTS', 30 | delegated_vesting_shares: '60666472 VESTS', 31 | received_vesting_shares: '191002659 VESTS' 32 | } 33 | 34 | let bar = client.rc.calculateVPMana(account) 35 | assert.equal(bar.percentage, 6181) 36 | account.voting_manabar.last_update_time = 1537064449 37 | bar = client.rc.calculateVPMana(account) 38 | assert.equal(bar.percentage, 10000) 39 | }) 40 | 41 | it('calculateRCMana', function() { 42 | let rc_account = { 43 | account: 'therealwolf', 44 | rc_manabar: { 45 | current_mana: '100000', 46 | last_update_time: 1537064449 47 | }, 48 | max_rc_creation_adjustment: { 49 | amount: '500', 50 | precision: 3, 51 | nai: '@@000000021' 52 | }, 53 | max_rc: '1000000' 54 | } 55 | 56 | let bar = client.rc.calculateRCMana(rc_account) 57 | assert.equal(bar.percentage, 10000) 58 | rc_account.rc_manabar.last_update_time = Date.now() / 1000 59 | bar = client.rc.calculateRCMana(rc_account) 60 | assert(bar.percentage >= 1000 && bar.percentage < 1100) 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /test/serializers.ts: -------------------------------------------------------------------------------- 1 | import 'mocha' 2 | import * as assert from 'assert' 3 | import * as ByteBuffer from 'bytebuffer' 4 | 5 | import {Types, Serializer, HexBuffer} from './../src' 6 | 7 | /* 8 | Serializer tests in the format: 9 | [{"name": "Type[::Subtype]", "values": [["expected output as hex string", ]]}] 10 | */ 11 | const serializerTests = require('./serializer-tests.json') 12 | 13 | function serialize(serializer: Serializer, data: any) { 14 | const buffer = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN) 15 | serializer(buffer, data) 16 | buffer.flip() 17 | return Buffer.from(buffer.toBuffer()).toString('hex') 18 | } 19 | 20 | describe('serializers', function() { 21 | 22 | for (const test of serializerTests) { 23 | it(test.name, () => { 24 | let serializer: Serializer 25 | if (test.name.indexOf('::') === -1) { 26 | serializer = Types[test.name] 27 | } else { 28 | const [base, ...sub] = test.name.split('::').map((t) => Types[t]) 29 | serializer = base(...sub) 30 | } 31 | for (const [expected, value] of test.values) { 32 | const actual = serialize(serializer, value) 33 | assert.equal(actual, expected) 34 | } 35 | }) 36 | } 37 | 38 | it('Binary', function() { 39 | const data = HexBuffer.from('026400c800') 40 | const r1 = serialize(Types.Binary(), HexBuffer.from([0x80, 0x00, 0x80])) 41 | assert.equal(r1, '03800080') 42 | const r2 = serialize(Types.Binary(), HexBuffer.from(Buffer.from('026400c800', 'hex'))) 43 | assert.equal(r2, '05026400c800') 44 | const r3 = serialize(Types.Binary(5), HexBuffer.from(data)) 45 | assert.equal(r3, '026400c800') 46 | assert.throws(() => { 47 | serialize(Types.Binary(10), data) 48 | }) 49 | }) 50 | 51 | it('Void', function() { 52 | assert.throws(() => { serialize(Types.Void, null) }) 53 | }) 54 | 55 | it('Invalid Operations', function() { 56 | assert.throws(() => { serialize(Types.Operation, ['transfer', {}]) }) 57 | assert.throws(() => { serialize(Types.Operation, ['transfer', {from: 1}]) }) 58 | assert.throws(() => { serialize(Types.Operation, ['transfer', 10]) }) 59 | }) 60 | 61 | }) 62 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file dsteem exports. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import * as utils from './utils' 37 | export {utils} 38 | 39 | export * from './helpers/blockchain' 40 | export * from './helpers/database' 41 | export * from './helpers/rc' 42 | 43 | export * from './steem/account' 44 | export * from './steem/asset' 45 | export * from './steem/block' 46 | export * from './steem/comment' 47 | export * from './steem/misc' 48 | export * from './steem/operation' 49 | export * from './steem/serializer' 50 | export * from './steem/transaction' 51 | 52 | export * from './client' 53 | export * from './crypto' 54 | -------------------------------------------------------------------------------- /examples/vote-bot/bot.js: -------------------------------------------------------------------------------- 1 | const dsteem = require('dsteem') 2 | 3 | // bot is configured with enviroment variables 4 | 5 | // the username of the bot 6 | const BOT_USER = process.env['BOT_USER'] || die('BOT_USER missing') 7 | // the posting key of the bot 8 | const POSTING_KEY = process.env['POSTING_KEY'] || die('POSTING_KEY missing') 9 | // the user we want to vote the same as 10 | const FOLLOW_USER = process.env['FOLLOW_USER'] || die('FOLLOW_USER missing') 11 | // and the vote weight to use, 10000 = 100% 12 | const VOTE_WEIGHT = process.env['VOTE_WEIGHT'] ? parseInt(process.env['VOTE_WEIGHT']) : 10000 13 | 14 | // setup the dsteem client, you can use other nodes, for example gtg's public node at https://gtg.steem.house:8090 15 | const client = new dsteem.Client('https://api.steemit.com') 16 | 17 | // deserialize the posting key (in wif format, same format as you find on the steemit.com interface) 18 | const key = dsteem.PrivateKey.from(POSTING_KEY) 19 | 20 | // create a new readable stream with all operations, we use the 'latest' mode since 21 | // we don't care about reversed block that much for a simple vote bot 22 | // and this will make it react faster to the votes of it's master 23 | const stream = client.blockchain.getOperationsStream({mode: dsteem.BlockchainMode.Latest}) 24 | 25 | console.log(`Following ${ FOLLOW_USER } with ${ VOTE_WEIGHT / 100 }% vote weight`) 26 | 27 | // the stream will emit one data event for every operatio that happens on the steem blockchain 28 | stream.on('data', (operation) => { 29 | 30 | // we only care about vote operations made by the user we follow 31 | if (operation.op[0] == 'vote') { 32 | let vote = operation.op[1] 33 | if (vote.voter === FOLLOW_USER) { 34 | console.log(`${ vote.voter } voted, following...`) 35 | 36 | // change the voter to the bot user and set the weight 37 | vote.voter = BOT_USER 38 | if (vote.weight > 0) { 39 | vote.weight = VOTE_WEIGHT 40 | } else { 41 | vote.weight = -VOTE_WEIGHT // follow flags as well 42 | } 43 | 44 | // finally broadcast the vote to the network 45 | client.broadcast.vote(vote, key).then(() => { 46 | console.log(`Voted for https://steemit.com/@${ vote.author }/${ vote.permlink }`) 47 | }).catch((error) => { 48 | console.warn('Vote failed', error) 49 | }) 50 | } 51 | } 52 | }) 53 | 54 | function die(msg) { process.stderr.write(msg+'\n'); process.exit(1) } 55 | -------------------------------------------------------------------------------- /src/steem/transaction.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Steem transaction type definitions. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import {Operation} from './operation' 37 | 38 | export interface Transaction { 39 | ref_block_num: number 40 | ref_block_prefix: number 41 | expiration: string 42 | operations: Operation[] 43 | extensions: any[] 44 | } 45 | 46 | export interface SignedTransaction extends Transaction { 47 | signatures: string[] 48 | } 49 | 50 | export interface TransactionConfirmation { 51 | id: string // transaction_id_type 52 | block_num: number // int32_t 53 | trx_num: number // int32_t 54 | expired: boolean 55 | } 56 | -------------------------------------------------------------------------------- /test/broadcast.ts: -------------------------------------------------------------------------------- 1 | import 'mocha' 2 | import * as assert from 'assert' 3 | import * as lorem from 'lorem-ipsum' 4 | import {VError} from 'verror' 5 | 6 | import {Client, PrivateKey, utils} from './../src' 7 | 8 | import {getTestnetAccounts, randomString, agent} from './common' 9 | 10 | describe('broadcast', function() { 11 | this.slow(10 * 1000) 12 | this.timeout(60 * 1000) 13 | 14 | const client = Client.testnet({agent}) 15 | 16 | let acc1, acc2: {username: string, password: string} 17 | before(async function() { 18 | [acc1, acc2] = await getTestnetAccounts() 19 | }) 20 | 21 | const postPermlink = `dsteem-test-${ randomString(7) }` 22 | 23 | it('should broadcast', async function() { 24 | const key = PrivateKey.fromLogin(acc1.username, acc1.password, 'posting') 25 | const body = [ 26 | `![picture](https://unsplash.it/1200/800?image=${ ~~(Math.random() * 1085) })`, 27 | '\n---\n', 28 | lorem({count: ~~(1 + Math.random() * 10), units: 'paragraphs'}), 29 | '\n\n🐢' 30 | ].join('\n') 31 | const result = await client.broadcast.comment({ 32 | parent_author: '', 33 | parent_permlink: 'test', 34 | author: acc1.username, 35 | permlink: postPermlink, 36 | title: `Picture of the day #${ ~~(Math.random() * 1e8) }`, 37 | body, 38 | json_metadata: JSON.stringify({foo: 'bar', tags: ['test']}), 39 | }, key) 40 | const block = await client.database.getBlock(result.block_num) 41 | assert(block.transaction_ids.indexOf(result.id) !== -1) 42 | }) 43 | 44 | it('should handle concurrent broadcasts', async function() { 45 | const key = PrivateKey.fromLogin(acc2.username, acc2.password, 'posting') 46 | const commentPromise = client.broadcast.comment({ 47 | parent_author: acc1.username, 48 | parent_permlink: postPermlink, 49 | author: acc2.username, 50 | permlink: `${ postPermlink }-botcomment-1`, 51 | title: 'Comments has titles?', 52 | body: `Amazing post! Revoted upsteemed and trailing! @${ acc2.username }`, 53 | json_metadata: '', 54 | }, key) 55 | const votePromise = client.broadcast.vote({ 56 | voter: acc2.username, 57 | author: acc1.username, 58 | permlink: postPermlink, 59 | weight: 10000, 60 | }, key) 61 | const result = await Promise.all([commentPromise, votePromise]) 62 | assert(result.every((r) => r.expired === false)) 63 | }) 64 | 65 | }) 66 | -------------------------------------------------------------------------------- /src/index-browser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file dsteem entry point for browsers. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import 'regenerator-runtime/runtime' 37 | 38 | // Microsoft is keeping to their long-held tradition of shipping broken 39 | // standards implementations, this forces Edge to use the polyfill insted. 40 | // tslint:disable-next-line:no-string-literal 41 | if (global['navigator'] && /Edge/.test(global['navigator'].userAgent)) { 42 | delete global['fetch'] // tslint:disable-line:no-string-literal 43 | } 44 | 45 | import 'core-js/es6/map' 46 | import 'core-js/es6/number' 47 | import 'core-js/es6/promise' 48 | import 'core-js/es6/symbol' 49 | import 'core-js/fn/array/from' 50 | import 'core-js/modules/es7.symbol.async-iterator' 51 | import 'whatwg-fetch' 52 | 53 | export * from './index' 54 | -------------------------------------------------------------------------------- /examples/comment-feed/contents/styles/main.styl: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Indie+Flower|Lato:300') 2 | 3 | @import 'nib' 4 | normalize-html5() 5 | 6 | theme-color = #c93c25 7 | theme-hue = hue(theme-color) 8 | theme-color-compl = hue(theme-color, theme-hue + 180deg) 9 | 10 | text-color = darken(desaturate(theme-color, 90%), 70%) 11 | background-color = lighten(desaturate(theme-color-compl, 40%), 90%) 12 | 13 | * 14 | box-sizing: border-box 15 | margin: 0 16 | padding: 0 17 | 18 | @import './typography.styl' 19 | @import './spinner.styl' 20 | @import './utils.styl' 21 | 22 | body 23 | background: background-color 24 | 25 | html 26 | @media (max-width: 800px) 27 | font-size: 14px 28 | 29 | a 30 | color: theme-color 31 | text-decoration: none 32 | &, &:visited 33 | color: theme-color 34 | &:hover 35 | text-decoration: underline 36 | 37 | input, body 38 | font-family: 'Lato', sans-serif 39 | color: text-color 40 | 41 | #no-js 42 | @extends .flex-center, .fixed-overlay 43 | display: none 44 | background: background-color 45 | .no-js & 46 | display: flex 47 | z-index: 10 48 | 49 | #loading 50 | @extends .flex-center, .fixed-overlay 51 | background: background-color 52 | transition: 300ms ease-out all 53 | opacity: 0 54 | visibility: hidden 55 | font-size: 0.8em 56 | p 57 | margin: 0 58 | padding-top: 2em 59 | span 60 | display: block 61 | width: 3em 62 | height: 3em 63 | @extends .spinner 64 | .loading & 65 | opacity: 1 66 | visibility: visible 67 | .error & 68 | span, p 69 | display: none 70 | &:before 71 | content: 'Error: ' attr(data-error) 72 | 73 | #controls 74 | transition: 300ms ease-out all 75 | opacity: 1 76 | .loading & 77 | opacity: 0 78 | position: fixed 79 | top: 0 80 | left: 0 81 | padding: 1.1em 2em 82 | background-color: desaturate(theme-color-compl, 30%) 83 | border-bottom: .2em solid background-color 84 | a 85 | color: white 86 | width: 100% 87 | display: flex 88 | justify-content: space-between 89 | 90 | #comments 91 | width: 100% 92 | overflow: hidden 93 | transition: 300ms ease-out all 94 | opacity: 1 95 | .loading & 96 | opacity: 0 97 | visibility: hidden 98 | padding: 1em 99 | padding-top: 2em 100 | .comment 101 | margin: 1em 102 | .author 103 | font-size: 0.8em 104 | line-height: 1.5 105 | display: block 106 | .body 107 | display: block 108 | font-size: 1.3em 109 | line-height: 1.2 110 | font-family: 'Indie Flower', cursive 111 | -------------------------------------------------------------------------------- /test/client.ts: -------------------------------------------------------------------------------- 1 | import 'mocha' 2 | import * as assert from 'assert' 3 | import {VError} from 'verror' 4 | 5 | import {Client, utils} from './../src' 6 | 7 | describe('client', function() { 8 | this.slow(200) 9 | this.timeout(30 * 1000) 10 | 11 | const client = Client.testnet() 12 | const aclient = client as any 13 | 14 | it('should make rpc call', async function() { 15 | const result = await client.call('condenser_api', 'get_accounts', [['initminer']]) as any[] 16 | assert.equal(result.length, 1) 17 | assert.equal(result[0].name, 'initminer') 18 | }) 19 | 20 | it('should handle rpc errors', async function() { 21 | try { 22 | await client.call('condenser_api', 'i_like_turtles') 23 | assert(false, 'should not be reached') 24 | } catch (error) { 25 | assert.equal(error.name, 'RPCError') 26 | assert(error.message == `itr != _by_name.end(): no method with name 'i_like_turtles'` // pre-appbase 27 | || error.message == `method_itr != api_itr->second.end(): Could not find method i_like_turtles`) // appbase 28 | 29 | const info = VError.info(error) 30 | assert.equal(info.code, 10) 31 | assert.equal(info.name, 'assert_exception') 32 | } 33 | }) 34 | 35 | it('should format rpc errors', async function() { 36 | const tx = {operations: [['witness_update', {}]]} 37 | try { 38 | await client.call('condenser_api', 'broadcast_transaction', [tx]) 39 | assert(false, 'should not be reached') 40 | } catch (error) { 41 | assert.equal(error.name, 'RPCError') 42 | assert.equal(error.message, 'is_valid_account_name( name ): Account name ${n} is invalid n=') 43 | const info = VError.info(error) 44 | assert.equal(info.code, 10) 45 | assert.equal(info.name, 'assert_exception') 46 | } 47 | }) 48 | 49 | it('should retry and timeout', async function() { 50 | this.slow(2500) 51 | aclient.timeout = 1000 52 | aclient.address = 'https://jnordberg.github.io/dsteem/FAIL' 53 | const backoff = aclient.backoff 54 | let seenBackoff = false 55 | aclient.backoff = (tries) => { 56 | seenBackoff = true 57 | return backoff(tries) 58 | } 59 | const tx = {operations: [['witness_update', {}]]} 60 | try { 61 | await client.database.getChainProperties() 62 | assert(false, 'should not be reached') 63 | } catch (error) { 64 | assert(seenBackoff, 'should have seen backoff') 65 | } 66 | }) 67 | 68 | }) 69 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | SHELL := /bin/bash 3 | PATH := ./node_modules/.bin:$(PATH) 4 | 5 | SRC_FILES := $(shell find src -name '*.ts') 6 | 7 | define VERSION_TEMPLATE 8 | "use strict"; 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | exports.default = '$(shell node -p 'require("./package.json").version')'; 11 | endef 12 | 13 | all: lib bundle docs 14 | 15 | export VERSION_TEMPLATE 16 | lib: $(SRC_FILES) node_modules 17 | tsc -p tsconfig.json --outDir lib && \ 18 | echo "$$VERSION_TEMPLATE" > lib/version.js 19 | touch lib 20 | 21 | dist/%.js: lib 22 | browserify $(filter-out $<,$^) --debug --full-paths \ 23 | --standalone dsteem --plugin tsify \ 24 | --transform [ babelify --extensions .ts ] \ 25 | | derequire > $@ 26 | uglifyjs $@ \ 27 | --source-map "content=inline,url=$(notdir $@).map,filename=$@.map" \ 28 | --compress "dead_code,collapse_vars,reduce_vars,keep_infinity,drop_console,passes=2" \ 29 | --output $@ || rm $@ 30 | 31 | dist/dsteem.js: src/index-browser.ts 32 | 33 | dist/dsteem.d.ts: $(SRC_FILES) node_modules 34 | dts-generator --name dsteem --project . --out dist/dsteem.d.ts 35 | sed -e "s@'dsteem/index'@'dsteem'@g" -i '' dist/dsteem.d.ts 36 | 37 | dist/%.gz: dist/dsteem.js 38 | gzip -9 -f -c $(basename $@) > $(basename $@).gz 39 | 40 | bundle: dist/dsteem.js.gz dist/dsteem.d.ts 41 | 42 | .PHONY: coverage 43 | coverage: node_modules 44 | nyc -r html -r text -e .ts -i ts-node/register mocha --exit --reporter nyan --require ts-node/register test/*.ts 45 | 46 | .PHONY: test 47 | test: node_modules 48 | mocha --exit --require ts-node/register -r test/_node.js test/*.ts --grep '$(grep)' 49 | 50 | .PHONY: ci-test 51 | ci-test: node_modules 52 | tslint -p tsconfig.json -c tslint.json 53 | nyc -r lcov -e .ts -i ts-node/register mocha --exit --reporter tap --require ts-node/register test/*.ts 54 | 55 | .PHONY: browser-test 56 | browser-test: dist/dsteem.js 57 | BUILD_NUMBER="$$(git rev-parse --short HEAD)-$$(date +%s)" \ 58 | karma start test/_karma-sauce.js 59 | 60 | .PHONY: browser-test-local 61 | browser-test-local: dist/dsteem.js 62 | karma start test/_karma.js 63 | 64 | .PHONY: lint 65 | lint: node_modules 66 | tslint -p tsconfig.json -c tslint.json -t stylish --fix 67 | 68 | node_modules: 69 | yarn install --non-interactive --frozen-lockfile 70 | 71 | docs: $(SRC_FILES) node_modules 72 | typedoc --gitRevision master --target ES6 --mode file --out docs src 73 | find docs -name "*.html" | xargs sed -i '' 's~$(shell pwd)~.~g' 74 | echo "Served at " > docs/README.md 75 | touch docs 76 | 77 | .PHONY: clean 78 | clean: 79 | rm -rf lib/ 80 | rm -f dist/* 81 | rm -rf docs/ 82 | 83 | .PHONY: distclean 84 | distclean: clean 85 | rm -rf node_modules/ 86 | -------------------------------------------------------------------------------- /src/steem/block.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Steem block type definitions. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import {Transaction} from './transaction' 37 | 38 | /** 39 | * Unsigned block header. 40 | */ 41 | export interface BlockHeader { 42 | previous: string // block_id_type 43 | timestamp: string // time_point_sec 44 | witness: string 45 | transaction_merkle_root: string // checksum_type 46 | extensions: any[] // block_header_extensions_type 47 | } 48 | 49 | /** 50 | * Signed block header. 51 | */ 52 | export interface SignedBlockHeader extends BlockHeader { 53 | witness_signature: string // signature_type 54 | } 55 | 56 | /** 57 | * Full signed block. 58 | */ 59 | export interface SignedBlock extends SignedBlockHeader { 60 | block_id: string 61 | signing_key: string 62 | transaction_ids: string[] 63 | transactions: Transaction[] 64 | } 65 | -------------------------------------------------------------------------------- /test/common.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as fs from 'fs' 3 | import * as https from 'https' 4 | import {randomBytes} from 'crypto' 5 | 6 | export const NUM_TEST_ACCOUNTS = 2 7 | export const IS_BROWSER = global['isBrowser'] === true 8 | export const TEST_NODE = process.env['TEST_NODE'] || 'https://api.steemit.com' 9 | 10 | export const agent = IS_BROWSER ? undefined : new https.Agent({keepAlive: true}) 11 | 12 | const fetch = global['fetch'] 13 | 14 | async function readFile(filename: string) { 15 | return new Promise((resolve, reject) => { 16 | fs.readFile(filename, (error, result) => { 17 | if (error) { reject(error) } else { resolve(result) } 18 | }) 19 | }) 20 | } 21 | 22 | async function writeFile(filename: string, data: Buffer) { 23 | return new Promise((resolve, reject) => { 24 | fs.writeFile(filename, data, (error) => { 25 | if (error) { reject(error) } else { resolve() } 26 | }) 27 | }) 28 | } 29 | 30 | export function randomString(length: number) { 31 | return randomBytes(length*2) 32 | .toString('base64') 33 | .replace(/[^0-9a-z]+/gi, '') 34 | .slice(0, length) 35 | .toLowerCase() 36 | } 37 | 38 | export async function createAccount(): Promise<{username: string, password: string}> { 39 | const password = randomString(32) 40 | const username = `dsteem-${ randomString(9) }` 41 | const response = await fetch('https://testnet.steem.vc/create', { 42 | method: 'POST', 43 | body: `username=${ username }&password=${ password }`, 44 | headers: {'Content-Type': 'application/x-www-form-urlencoded'}, 45 | }) 46 | const text = await response.text() 47 | if (response.status !== 200) { 48 | throw new Error(`Unable to create user: ${ text }`) 49 | } 50 | return {username, password} 51 | } 52 | 53 | export async function getTestnetAccounts(): Promise<{username: string, password: string}[]> { 54 | if (!IS_BROWSER) { 55 | try { 56 | const data = await readFile('.testnetrc') 57 | return JSON.parse(data.toString()) 58 | } catch (error) { 59 | if (error.code !== 'ENOENT') { 60 | throw error 61 | } 62 | } 63 | } else if (global['__testnet_accounts']) { 64 | return global['__testnet_accounts'] 65 | } 66 | let rv: {username: string, password: string}[] = [] 67 | while (rv.length < NUM_TEST_ACCOUNTS) { 68 | rv.push(await createAccount()) 69 | } 70 | if (console && console.log) { 71 | console.log(`CREATED TESTNET ACCOUNTS: ${ rv.map((i)=>i.username) }`) 72 | } 73 | if (!IS_BROWSER) { 74 | await writeFile('.testnetrc', Buffer.from(JSON.stringify(rv))) 75 | } else { 76 | global['__testnet_accounts'] = rv 77 | } 78 | return rv 79 | } -------------------------------------------------------------------------------- /src/helpers/rc.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-string-literal */ 2 | 3 | import { Client } from './../client' 4 | import { Account } from './../steem/account' 5 | import { getVests } from './../steem/misc' 6 | import { Manabar, RCAccount, RCParams, RCPool } from './../steem/rc' 7 | 8 | export class RCAPI { 9 | constructor(readonly client: Client) { } 10 | 11 | /** 12 | * Convenience for calling `rc_api`. 13 | */ 14 | public call(method: string, params?: any) { 15 | return this.client.call('rc_api', method, params) 16 | } 17 | 18 | /** 19 | * Returns RC data for array of usernames 20 | */ 21 | public async findRCAccounts(usernames: string[]): Promise { 22 | return (await this.call('find_rc_accounts', { accounts: usernames }))['rc_accounts'] 23 | } 24 | 25 | /** 26 | * Returns the global resource params 27 | */ 28 | public async getResourceParams(): Promise { 29 | return (await this.call('get_resource_params', {}))['resource_params'] 30 | } 31 | 32 | /** 33 | * Returns the global resource pool 34 | */ 35 | public async getResourcePool(): Promise { 36 | return (await this.call('get_resource_pool', {}))['resource_pool'] 37 | } 38 | 39 | /** 40 | * Makes a API call and returns the RC mana-data for a specified username 41 | */ 42 | public async getRCMana(username: string): Promise { 43 | const rc_account: RCAccount = (await this.findRCAccounts([username]))[0] 44 | return this.calculateRCMana(rc_account) 45 | } 46 | 47 | /** 48 | * Makes a API call and returns the VP mana-data for a specified username 49 | */ 50 | public async getVPMana(username: string): Promise { 51 | const account: Account = (await this.client.call(`condenser_api`, 'get_accounts', [[username]]))[0] 52 | return this.calculateVPMana(account) 53 | } 54 | 55 | /** 56 | * Calculates the RC mana-data based on an RCAccount - findRCAccounts() 57 | */ 58 | public calculateRCMana(rc_account: RCAccount): Manabar { 59 | return this._calculateManabar(Number(rc_account.max_rc), rc_account.rc_manabar) 60 | } 61 | 62 | /** 63 | * Calculates the RC mana-data based on an Account - getAccounts() 64 | */ 65 | public calculateVPMana(account: Account): Manabar { 66 | const max_mana: number = getVests(account) * Math.pow(10, 6) 67 | return this._calculateManabar(max_mana, account.voting_manabar) 68 | } 69 | 70 | /** 71 | * Internal convenience method to reduce redundant code 72 | */ 73 | private _calculateManabar(max_mana: number, { current_mana, last_update_time }): Manabar { 74 | const delta: number = Date.now() / 1000 - last_update_time 75 | current_mana = Number(current_mana) + (delta * max_mana / 432000) 76 | let percentage: number = Math.round(current_mana / max_mana * 10000) 77 | 78 | if (!isFinite(percentage) || percentage < 0) { 79 | percentage = 0 80 | } else if (percentage > 10000) { 81 | percentage = 10000 82 | } 83 | 84 | return { current_mana, max_mana, percentage } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /examples/comment-feed/contents/scripts/main.ts: -------------------------------------------------------------------------------- 1 | 2 | import {Client, BlockchainMode} from 'dsteem' 3 | 4 | import * as removeMarkdown from 'remove-markdown' 5 | 6 | const client = new Client('https://api.steemit.com') 7 | 8 | function sleep(ms: number): Promise { 9 | return new Promise((resolve) => { 10 | setTimeout(resolve, ms) 11 | }) 12 | } 13 | 14 | function shortBody(body: string) { 15 | let rv: string = removeMarkdown(body).replace(/<[^>]*>/g, '') 16 | if (rv.length > 140) { 17 | return rv.slice(0, 139) + '…' 18 | } 19 | return rv 20 | } 21 | 22 | function buildComment(comment: any): HTMLDivElement { 23 | const rv = document.createElement('div') 24 | rv.className = 'comment' 25 | 26 | const {author, body, parent_author, parent_permlink} = comment 27 | const parent = `@${ parent_author }/${ parent_permlink }` 28 | 29 | rv.innerHTML += ` 30 | 31 | @${ author } 32 | 33 | ${ shortBody(body) } 34 | ` 35 | 36 | return rv 37 | } 38 | 39 | async function *getComments() { 40 | for await (const operation of client.blockchain.getOperations({mode: BlockchainMode.Latest})) { 41 | if (operation.op[0] === 'comment') { 42 | const comment = operation.op[1] 43 | if (comment.body.slice(0, 2) == '@@') { 44 | continue // skip edits 45 | } 46 | yield comment 47 | } 48 | } 49 | } 50 | 51 | export default async function main() { 52 | const commentsEl = document.getElementById('comments') 53 | const backlog = [] 54 | 55 | let renderTimer: NodeJS.Timer | undefined 56 | 57 | document.querySelector('a[href="#pause"]').addEventListener('click', function(event) { 58 | event.preventDefault() 59 | if (renderTimer) { 60 | this.textContent = 'Resume' 61 | clearTimeout(renderTimer) 62 | renderTimer = undefined 63 | } else { 64 | this.textContent = 'Pause' 65 | render() 66 | } 67 | }) 68 | 69 | function render() { 70 | const comment = backlog.shift() 71 | if (comment) { 72 | commentsEl.appendChild(buildComment(comment)) 73 | while (commentsEl.children.length > 100) { 74 | commentsEl.removeChild(commentsEl.children[0]) 75 | } 76 | window.scrollTo(undefined, document.body.scrollHeight) 77 | } 78 | const next = 3000 / (backlog.length + 1) 79 | renderTimer = setTimeout(render, next) 80 | } 81 | 82 | async function *update() { 83 | for await (const comment of getComments()) { 84 | backlog.push(comment) 85 | yield 86 | } 87 | } 88 | 89 | let iter = update() 90 | 91 | const run = async () => { 92 | try { 93 | while (true) { await iter.next() } 94 | } catch (error) { 95 | console.error('Problem fetching comments', error) 96 | setTimeout(() => { 97 | iter = update() 98 | run() 99 | }, 3000) 100 | } 101 | } 102 | 103 | await iter.next() // wait for the first update 104 | run() // run rest of the generator non-blocking 105 | render() // start render loop 106 | } 107 | -------------------------------------------------------------------------------- /src/steem/comment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Steem type definitions related to comments and posting. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import {Asset} from './asset' 37 | 38 | export interface Comment { 39 | id: number // comment_id_type 40 | category: string 41 | parent_author: string // account_name_type 42 | parent_permlink: string 43 | author: string // account_name_type 44 | permlink: string 45 | title: string 46 | body: string 47 | json_metadata: string 48 | last_update: string // time_point_sec 49 | created: string // time_point_sec 50 | active: string // time_point_sec 51 | last_payout: string // time_point_sec 52 | depth: number // uint8_t 53 | children: number // uint32_t 54 | net_rshares: string // share_type 55 | abs_rshares: string // share_type 56 | vote_rshares: string // share_type 57 | children_abs_rshares: string // share_type 58 | cashout_time: string // time_point_sec 59 | max_cashout_time: string // time_point_sec 60 | total_vote_weight: number // uint64_t 61 | reward_weight: number // uint16_t 62 | total_payout_value: Asset | string 63 | curator_payout_value: Asset | string 64 | author_rewards: string // share_type 65 | net_votes: number // int32_t 66 | root_comment: number // comment_id_type 67 | max_accepted_payout: string // asset 68 | percent_steem_dollars: number // uint16_t 69 | allow_replies: boolean 70 | allow_votes: boolean 71 | allow_curation_rewards: boolean 72 | beneficiaries: BeneficiaryRoute[] 73 | } 74 | 75 | /** 76 | * Discussion a.k.a. Post. 77 | */ 78 | export interface Discussion extends Comment { 79 | url: string // /category/@rootauthor/root_permlink#author/permlink 80 | root_title: string 81 | pending_payout_value: Asset | string 82 | total_pending_payout_value: Asset | string 83 | active_votes: any[] // vote_state[] 84 | replies: string[] /// author/slug mapping 85 | author_reputation: number // share_type 86 | promoted: Asset | string 87 | body_length: string // Bignum 88 | reblogged_by: any[] // account_name_type[] 89 | first_reblogged_by?: any // account_name_type 90 | first_reblogged_on?: any // time_point_sec 91 | } 92 | 93 | export interface BeneficiaryRoute { 94 | account: string // account_name_type 95 | weight: number // uint16_t 96 | } 97 | -------------------------------------------------------------------------------- /test/serializer-tests.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Asset", 4 | "values": [ 5 | ["102700000000000003535445454d0000", "10.000 STEEM"], 6 | ["081a99be1c0000000656455354530000", "123456.789000 VESTS"], 7 | ["1378feffffffffff0353424400000000", "-100.333 SBD"] 8 | ] 9 | }, 10 | { 11 | "name": "Array::String", 12 | "values": [ 13 | [ 14 | "0203666f6f03626172", 15 | ["foo", "bar"] 16 | ] 17 | ] 18 | }, 19 | { 20 | "name": "Array::UInt16", 21 | "values": [ 22 | [ 23 | "026400c800", 24 | [100, 200] 25 | ] 26 | ] 27 | }, 28 | { 29 | "name": "Boolean", 30 | "values": [ 31 | ["00", false], 32 | ["01", true] 33 | ] 34 | }, 35 | { 36 | "name": "Date", 37 | "values": [ 38 | ["07486a59", "2017-07-15T16:51:19"], 39 | ["80436d38", "2000-01-01T00:00:00"] 40 | ] 41 | }, 42 | { 43 | "name": "FlatMap::UInt8::UInt8", 44 | "values": [ 45 | [ 46 | "02beefface", 47 | [ 48 | [190, 239], 49 | [250, 206] 50 | ] 51 | ] 52 | ] 53 | }, 54 | { 55 | "name": "FlatMap::String::Date", 56 | "values": [ 57 | [ 58 | "0102326b80436d38", 59 | [ 60 | ["2k", "2000-01-01T00:00:00"] 61 | ] 62 | ] 63 | ] 64 | }, 65 | { 66 | "name": "Int8", 67 | "values": [ 68 | ["00", 0], 69 | ["80", -128], 70 | ["7f", 127] 71 | ] 72 | }, 73 | { 74 | "name": "Int16", 75 | "values": [ 76 | ["0080", -32768], 77 | ["ff00", 255], 78 | ["beef", 61374] 79 | ] 80 | }, 81 | { 82 | "name": "Int32", 83 | "values": [ 84 | ["cefaefbe", 3203398350], 85 | ["00000000", 0], 86 | ["ffffff7f", 2147483647] 87 | ] 88 | }, 89 | { 90 | "name": "Int64", 91 | "values": [ 92 | ["0000000000000000", 0], 93 | ["ff00000000000000", 255], 94 | ["ffffffffffff1f00", 9007199254740991], 95 | ["010000000000e0ff", -9007199254740991] 96 | ] 97 | }, 98 | { 99 | "name": "UInt8", 100 | "values": [ 101 | ["00", 0], 102 | ["ff", 255], 103 | ["7f", 127] 104 | ] 105 | }, 106 | { 107 | "name": "UInt16", 108 | "values": [ 109 | ["0100", 1], 110 | ["ff00", 255], 111 | ["ffff", 65535] 112 | ] 113 | }, 114 | { 115 | "name": "UInt32", 116 | "values": [ 117 | ["00000000", 0], 118 | ["ffffffff", 4294967295] 119 | ] 120 | }, 121 | { 122 | "name": "UInt64", 123 | "values": [ 124 | ["0000000000000000", 0], 125 | ["ffffffffffff1f00", 9007199254740991] 126 | ] 127 | }, 128 | { 129 | "name": "Optional::UInt8", 130 | "values": [ 131 | ["00", null], 132 | ["01ff", 255] 133 | ] 134 | }, 135 | { 136 | "name": "Operation", 137 | "values": [ 138 | [ 139 | "0203666f6f03626172e80300000000000003535445454d00000f77656464696e672070726573656e74", 140 | ["transfer", { 141 | "amount": 1, "from": "foo", "memo": "wedding present", "to": "bar" 142 | }] 143 | ] 144 | ] 145 | }, 146 | { 147 | "name": "Transaction", 148 | "values": [ 149 | [ 150 | "d204f776e54207486a59010003666f6f036261720362617a1027010a6c6f6e672d70616e7473", 151 | { 152 | "expiration": "2017-07-15T16:51:19", 153 | "extensions": ["long-pants"], 154 | "operations": [ 155 | ["vote", { 156 | "author": "bar", "permlink": "baz", "voter": "foo", "weight": 10000 157 | }] 158 | ], 159 | "ref_block_num": 1234, 160 | "ref_block_prefix": 1122334455 161 | } 162 | ] 163 | ] 164 | }, 165 | { 166 | "name": "String", 167 | "values": [ 168 | ["1548656c6cc3b6206672c3b66d205377c3a464656e21", "Hellö fröm Swäden!"], 169 | ["15e5a4a7e3818de381aae3818ae381a3e381b1e38184", "大きなおっぱい"], 170 | ["00", ""] 171 | ] 172 | }, 173 | { 174 | "name": "Price", 175 | "values": [ 176 | ["e80300000000000003535445454d00000b020000000000000353424400000000", {"base": "1.000 STEEM", "quote": "0.523 SBD"}] 177 | ] 178 | }, 179 | { 180 | "name": "PublicKey", 181 | "values": [ 182 | ["021ec205b7c084b96814310c8acb4a0048e82b236f1878acc273fd1cfd03dac7e1", "STM5832HKCJzs6K3rRCsZ1PidTKgjF38ZJb718Y3pCW92HEMsCGPf"] 183 | ] 184 | } 185 | ] -------------------------------------------------------------------------------- /test/asset.ts: -------------------------------------------------------------------------------- 1 | import 'mocha' 2 | import * as assert from 'assert' 3 | 4 | import {Asset, Price, getVestingSharePrice} from './../src' 5 | 6 | describe('asset', function() { 7 | 8 | it('should create from string', function() { 9 | const oneSteem = Asset.fromString('1.000 STEEM') 10 | assert.equal(oneSteem.amount, 1) 11 | assert.equal(oneSteem.symbol, 'STEEM') 12 | const vests = Asset.fromString('0.123456 VESTS') 13 | assert.equal(vests.amount, 0.123456) 14 | assert.equal(vests.symbol, 'VESTS') 15 | const sbd = Asset.from('0.444 SBD') 16 | assert.equal(sbd.amount, 0.444) 17 | assert.equal(sbd.symbol, 'SBD') 18 | }) 19 | 20 | it('should convert to string', function() { 21 | const steem = new Asset(44.999999, 'STEEM') 22 | assert.equal(steem.toString(), '45.000 STEEM') 23 | const vests = new Asset(44.999999, 'VESTS') 24 | assert.equal(vests.toString(), '44.999999 VESTS') 25 | }) 26 | 27 | it('should add and subtract', function() { 28 | const a = new Asset(44.999, 'STEEM') 29 | assert.equal(a.subtract(1.999).toString(), '43.000 STEEM') 30 | assert.equal(a.add(0.001).toString(), '45.000 STEEM') 31 | assert.equal(Asset.from('1.999 STEEM').subtract(a).toString(), '-43.000 STEEM') 32 | assert.equal(Asset.from(a).subtract(a).toString(), '0.000 STEEM') 33 | assert.equal(Asset.from('99.999999 VESTS').add('0.000001 VESTS').toString(), '100.000000 VESTS') 34 | assert.throws(() => Asset.fromString('100.000 STEEM').subtract('100.000000 VESTS')) 35 | assert.throws(() => Asset.from(100, 'VESTS').add(a)) 36 | assert.throws(() => Asset.from(100).add('1.000000 VESTS')) 37 | }) 38 | 39 | it('should max and min', function() { 40 | const a = Asset.from(1), b = Asset.from(2) 41 | assert.equal(Asset.min(a, b), a) 42 | assert.equal(Asset.min(b, a), a) 43 | assert.equal(Asset.max(a, b), b) 44 | assert.equal(Asset.max(b, a), b) 45 | }) 46 | 47 | it('should throw on invalid values', function() { 48 | assert.throws(() => Asset.fromString('1.000 SNACKS')) 49 | assert.throws(() => Asset.fromString('I LIKE TURT 0.42')) 50 | assert.throws(() => Asset.fromString('Infinity STEEM')) 51 | assert.throws(() => Asset.fromString('..0 STEEM')) 52 | assert.throws(() => Asset.from('..0 STEEM')) 53 | assert.throws(() => Asset.from(NaN)) 54 | assert.throws(() => Asset.from(false as any)) 55 | assert.throws(() => Asset.from(Infinity)) 56 | assert.throws(() => Asset.from({bar:22} as any)) 57 | }) 58 | 59 | it('should parse price', function() { 60 | const price1 = new Price(Asset.from('1.000 STEEM'), Asset.from(1, 'SBD')) 61 | const price2 = Price.from(price1) 62 | const price3 = Price.from({base: '1.000 STEEM', quote: price1.quote}) 63 | assert.equal(price1.toString(), '1.000 STEEM:1.000 SBD') 64 | assert.equal(price2.base.toString(), price3.base.toString()) 65 | assert.equal(price2.quote.toString(), price3.quote.toString()) 66 | }) 67 | 68 | it('should get vesting share price', function() { 69 | const props: any = { 70 | total_vesting_fund_steem: '5.000 STEEM', 71 | total_vesting_shares: '12345.000000 VESTS', 72 | } 73 | const price1 = getVestingSharePrice(props) 74 | assert.equal(price1.base.amount, 12345) 75 | assert.equal(price1.base.symbol, 'VESTS') 76 | assert.equal(price1.quote.amount, 5) 77 | assert.equal(price1.quote.symbol, 'STEEM') 78 | const badProps: any = { 79 | total_vesting_fund_steem: '0.000 STEEM', 80 | total_vesting_shares: '0.000000 VESTS', 81 | } 82 | const price2 = getVestingSharePrice(badProps) 83 | assert.equal(price2.base.amount, 1) 84 | assert.equal(price2.base.symbol, 'VESTS') 85 | assert.equal(price2.quote.amount, 1) 86 | assert.equal(price2.quote.symbol, 'STEEM') 87 | }) 88 | 89 | it('should convert price', function() { 90 | const price1 = new Price(Asset.from('0.500 STEEM'), Asset.from('1.000 SBD')) 91 | const v1 = price1.convert(Asset.from('1.000 STEEM')) 92 | assert.equal(v1.amount, 2) 93 | assert.equal(v1.symbol, 'SBD') 94 | const v2 = price1.convert(Asset.from('1.000 SBD')) 95 | assert.equal(v2.amount, 0.5) 96 | assert.equal(v2.symbol, 'STEEM') 97 | assert.throws(() => { 98 | price1.convert(Asset.from(1, 'VESTS')) 99 | }) 100 | }) 101 | 102 | }) 103 | 104 | -------------------------------------------------------------------------------- /test/blockchain.ts: -------------------------------------------------------------------------------- 1 | import 'mocha' 2 | import * as assert from 'assert' 3 | 4 | import {Client, SignedBlock, AppliedOperation, BlockchainMode} from './../src' 5 | 6 | import {agent, TEST_NODE} from './common' 7 | 8 | describe('blockchain', function() { 9 | this.slow(5 * 1000) 10 | this.timeout(60 * 1000) 11 | 12 | const client = new Client(TEST_NODE, {agent}) 13 | 14 | const expectedIds = ['0000000109833ce528d5bbfb3f6225b39ee10086', 15 | '00000002ed04e3c3def0238f693931ee7eebbdf1'] 16 | const expectedOps = ['vote', 'vote', 'comment', 'vote', 'vote', 'vote', 'vote', 17 | 'custom_json', 'producer_reward', 'author_reward', 'fill_vesting_withdraw', 18 | 'fill_vesting_withdraw', 'comment', 'comment', 'vote', 'vote', 19 | 'vote', 'vote', 'comment', 'custom_json', 'custom_json', 20 | 'custom_json', 'custom_json', 'claim_reward_balance', 21 | 'custom_json', 'vote', 'comment', 'comment_options', 22 | 'custom_json', 'vote', 'producer_reward', 'curation_reward', 'author_reward', 23 | 'fill_vesting_withdraw', 'fill_vesting_withdraw' ] 24 | 25 | it('should yield blocks', async function() { 26 | let ids: string[] = [] 27 | for await (const block of client.blockchain.getBlocks({from: 1, to: 2})) { 28 | ids.push(block.block_id) 29 | } 30 | assert.deepEqual(ids, expectedIds) 31 | }) 32 | 33 | it('should stream blocks', async function() { 34 | await new Promise((resolve, reject) => { 35 | const stream = client.blockchain.getBlockStream({from: 1, to: 2}) 36 | let ids: string[] = [] 37 | stream.on('data', (block: SignedBlock) => { 38 | ids.push(block.block_id) 39 | }) 40 | stream.on('error', reject) 41 | stream.on('end', () => { 42 | assert.deepEqual(ids, expectedIds) 43 | resolve() 44 | }) 45 | }) 46 | }) 47 | 48 | it('should yield operations', async function() { 49 | let ops: string[] = [] 50 | for await (const operation of client.blockchain.getOperations({from: 13300000, to: 13300001})) { 51 | ops.push(operation.op[0]) 52 | } 53 | assert.deepEqual(ops, expectedOps) 54 | }) 55 | 56 | it('should stream operations', async function() { 57 | await new Promise((resolve, reject) => { 58 | const stream = client.blockchain.getOperationsStream({from: 13300000, to: 13300001}) 59 | let ops: string[] = [] 60 | stream.on('data', (operation: AppliedOperation) => { 61 | ops.push(operation.op[0]) 62 | }) 63 | stream.on('error', reject) 64 | stream.on('end', () => { 65 | assert.deepEqual(ops, expectedOps) 66 | resolve() 67 | }) 68 | }) 69 | }) 70 | 71 | it('should yield latest blocks', async function() { 72 | const latest = await client.blockchain.getCurrentBlock(BlockchainMode.Latest) 73 | for await (const block of client.blockchain.getBlocks({mode: BlockchainMode.Latest})) { 74 | if (block.block_id === latest.block_id) { 75 | continue 76 | } 77 | assert.equal(block.previous, latest.block_id, 'should have the same block id') 78 | break 79 | } 80 | }) 81 | 82 | it('should handle errors on stream', async function() { 83 | await new Promise((resolve, reject) => { 84 | const stream = client.blockchain.getBlockStream(Number.MAX_VALUE) 85 | stream.on('data', () => { 86 | assert(false, 'unexpected stream data') 87 | }) 88 | stream.on('error', (error) => { 89 | resolve() 90 | }) 91 | }) 92 | }) 93 | 94 | it('should get block number stream', async function() { 95 | const current = await client.blockchain.getCurrentBlockNum() 96 | await new Promise(async (resolve, reject) => { 97 | const stream = client.blockchain.getBlockNumberStream() 98 | stream.on('data', (num) => { 99 | assert(num >= current) 100 | resolve() 101 | }) 102 | stream.on('error', reject) 103 | }) 104 | }) 105 | 106 | it('should get current block header', async function() { 107 | const now = Date.now() 108 | const header = await client.blockchain.getCurrentBlockHeader() 109 | const ts = new Date(header.timestamp+'Z').getTime() 110 | assert(Math.abs((ts / 1000) - (now / 1000)) < 120, 'blockheader timestamp too old') 111 | }) 112 | 113 | }) 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # [dsteem](https://github.com/jnordberg/dsteem) [![Build Status](https://img.shields.io/circleci/project/github/jnordberg/dsteem.svg?style=flat-square)](https://circleci.com/gh/jnordberg/workflows/dsteem) [![Coverage Status](https://img.shields.io/coveralls/jnordberg/dsteem.svg?style=flat-square)](https://coveralls.io/github/jnordberg/dsteem?branch=master) [![Package Version](https://img.shields.io/npm/v/dsteem.svg?style=flat-square)](https://www.npmjs.com/package/dsteem) 3 | 4 | Robust [steem blockchain](https://steem.io) client library that runs in both node.js and the browser. 5 | 6 | * [Demo](https://comments.steem.vc) ([source](https://github.com/jnordberg/dsteem/tree/master/examples/comment-feed)) 7 | * [Code playground](https://playground.steem.vc) 8 | * [Documentation](https://jnordberg.github.io/dsteem/) 9 | * [Bug tracker](https://github.com/jnordberg/dsteem/issues) 10 | 11 | --- 12 | 13 | **note** As of version 0.7.0 WebSocket support has been removed. The only transport provided now is HTTP(2). For most users the only change required is to swap `wss://` to `https://` in the address. If you run your own full node make sure to set the proper [CORS headers](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) if you plan to access it from a browser. 14 | 15 | --- 16 | 17 | 18 | Browser compatibility 19 | --------------------- 20 | 21 | [![Build Status](https://saucelabs.com/browser-matrix/jnordberg-dsteem.svg)](https://saucelabs.com/open_sauce/user/jnordberg-dsteem) 22 | 23 | 24 | Installation 25 | ------------ 26 | 27 | ### Via npm 28 | 29 | For node.js or the browser with [browserify](https://github.com/substack/node-browserify) or [webpack](https://github.com/webpack/webpack). 30 | 31 | ``` 32 | npm install dsteem 33 | ``` 34 | 35 | ### From cdn or self-hosted script 36 | 37 | Grab `dist/dsteem.js` from a [release](https://github.com/jnordberg/dsteem/releases) and include in your html: 38 | 39 | ```html 40 | 41 | ``` 42 | 43 | Or from the [unpkg](https://unpkg.com) cdn: 44 | 45 | ```html 46 | 47 | ``` 48 | 49 | Make sure to set the version you want when including from the cdn, you can also use `dsteem@latest` but that is not always desirable. See [unpkg.com](https://unpkg.com) for more information. 50 | 51 | 52 | Usage 53 | ----- 54 | 55 | ### In the browser 56 | 57 | ```html 58 | 59 | 67 | ``` 68 | 69 | See the [demo source](https://github.com/jnordberg/dsteem/tree/master/examples/comment-feed) for an example on how to setup a livereloading TypeScript pipeline with [wintersmith](https://github.com/jnordberg/wintersmith) and [browserify](https://github.com/substack/node-browserify). 70 | 71 | ### In node.js 72 | 73 | With TypeScript: 74 | 75 | ```typescript 76 | import {Client} from 'dsteem' 77 | 78 | const client = new Client('https://api.steemit.com') 79 | 80 | for await (const block of client.blockchain.getBlocks()) { 81 | console.log(`New block, id: ${ block.block_id }`) 82 | } 83 | ``` 84 | 85 | With JavaScript: 86 | 87 | ```javascript 88 | var dsteem = require('dsteem') 89 | 90 | var client = new dsteem.Client('https://api.steemit.com') 91 | var key = dsteem.PrivateKey.fromLogin('username', 'password', 'posting') 92 | 93 | client.broadcast.vote({ 94 | voter: 'username', 95 | author: 'almost-digital', 96 | permlink: 'dsteem-is-the-best', 97 | weight: 10000 98 | }, key).then(function(result){ 99 | console.log('Included in block: ' + result.block_num) 100 | }, function(error) { 101 | console.error(error) 102 | }) 103 | ``` 104 | 105 | With ES2016 (node.js 7+): 106 | 107 | ```javascript 108 | const {Client} = require('dsteem') 109 | 110 | const client = new Client('https://api.steemit.com') 111 | 112 | async function main() { 113 | const props = await client.database.getChainProperties() 114 | console.log(`Maximum blocksize consensus: ${ props.maximum_block_size } bytes`) 115 | client.disconnect() 116 | } 117 | 118 | main().catch(console.error) 119 | ``` 120 | 121 | With node.js streams: 122 | 123 | ```javascript 124 | var dsteem = require('dsteem') 125 | var es = require('event-stream') // npm install event-stream 126 | var util = require('util') 127 | 128 | var client = new dsteem.Client('https://api.steemit.com') 129 | 130 | var stream = client.blockchain.getBlockStream() 131 | 132 | stream.pipe(es.map(function(block, callback) { 133 | callback(null, util.inspect(block, {colors: true, depth: null}) + '\n') 134 | })).pipe(process.stdout) 135 | ``` 136 | 137 | 138 | Bundling 139 | -------- 140 | 141 | The easiest way to bundle dsteem (with browserify, webpack etc.) is to just `npm install dsteem` and `require('dsteem')` which will give you well-tested (see browser compatibility matrix above) pre-bundled code guaranteed to JustWork™. However, that is not always desirable since it will not allow your bundler to de-duplicate any shared dependencies dsteem and your app might have. 142 | 143 | To allow for deduplication you can `require('dsteem/lib/index-browser')`, or if you plan to provide your own polyfills: `require('dsteem/lib/index')`. See `src/index-browser.ts` for a list of polyfills expected. 144 | 145 | --- 146 | 147 | *Share and Enjoy!* 148 | -------------------------------------------------------------------------------- /test/crypto.ts: -------------------------------------------------------------------------------- 1 | import 'mocha' 2 | import * as assert from 'assert' 3 | import * as ByteBuffer from 'bytebuffer' 4 | import {inspect} from 'util' 5 | import {randomBytes, createHash} from 'crypto' 6 | 7 | import { 8 | DEFAULT_ADDRESS_PREFIX, 9 | DEFAULT_CHAIN_ID, 10 | Operation, 11 | PrivateKey, 12 | PublicKey, 13 | Signature, 14 | cryptoUtils, 15 | Transaction, 16 | Types, 17 | } from './../src' 18 | 19 | describe('crypto', function() { 20 | 21 | const testnetPrefix = 'STX' 22 | const testnetPair = { 23 | private: '5JQy7moK9SvNNDxn8rKNfQYFME5VDYC2j9Mv2tb7uXV5jz3fQR8', 24 | public: 'STX8FiV6v7yqYWTZz8WuFDckWr62L9X34hCy6koe8vd2cDJHimtgM', 25 | } 26 | const mainPair = { 27 | private: '5K2yDAd9KAZ3ZitBsAPyRka9PLFemUrbcL6UziZiPaw2c6jCeLH', 28 | public: 'STM8QykigLRi9ZUcNy1iXGY3KjRuCiLM8Ga49LHti1F8hgawKFc3K', 29 | } 30 | const mainPairPub = Buffer.from('03d0519ddad62bd2a833bee5dc04011c08f77f66338c38d99c685dee1f454cd1b8', 'hex') 31 | 32 | const testSig = '202c52188b0ecbc26c766fe6d3ec68dac58644f43f43fc7d97da122f76fa028f98691dd48b44394bdd8cecbbe66e94795dcf53291a1ef7c16b49658621273ea68e' 33 | const testKey = PrivateKey.from(randomBytes(32)) 34 | 35 | it('should decode public keys', function() { 36 | const k1 = PublicKey.fromString(testnetPair.public) 37 | assert.equal(k1.prefix, testnetPrefix) 38 | assert(k1.toString(), testnetPair.public) 39 | const k2 = PublicKey.from(mainPair.public) 40 | assert(k2.toString(), mainPair.public) 41 | const k3 = new PublicKey(mainPairPub, 'STM') 42 | assert(k2.toString(), k3.toString()) 43 | const k4 = PublicKey.from(testnetPair.public) 44 | assert(k4.toString(), testnetPair.public) 45 | }) 46 | 47 | it('should decode private keys', function() { 48 | const k1 = PrivateKey.fromString(testnetPair.private) 49 | assert(k1.toString(), testnetPair.private) 50 | const k2 = PrivateKey.from(mainPair.private) 51 | assert(k2.toString(), mainPair.private) 52 | }) 53 | 54 | it('should create public from private', function() { 55 | const key = PrivateKey.fromString(testnetPair.private) 56 | assert(key.createPublic().toString(), testnetPair.public) 57 | }) 58 | 59 | it('should handle prefixed keys', function() { 60 | const key = PublicKey.from(testnetPair.public) 61 | assert(key.toString(), testnetPair.public) 62 | assert(PrivateKey.fromString(testnetPair.private).createPublic(testnetPrefix).toString(), testnetPair.public) 63 | }) 64 | 65 | it('should conceal private key when inspecting', function() { 66 | const key = PrivateKey.fromString(testnetPair.private) 67 | assert.equal(inspect(key), 'PrivateKey: 5JQy7m...z3fQR8') 68 | assert.equal(inspect(key.createPublic(testnetPrefix)), 'PublicKey: STX8FiV6v7yqYWTZz8WuFDckWr62L9X34hCy6koe8vd2cDJHimtgM') 69 | }) 70 | 71 | it('should sign and verify', function() { 72 | const message = randomBytes(32) 73 | const signature = testKey.sign(message) 74 | assert(testKey.createPublic().verify(message, signature)) 75 | signature.data.writeUInt8(0x42, 3) 76 | assert(!testKey.createPublic().verify(message, signature)) 77 | }) 78 | 79 | it('should de/encode signatures', function() { 80 | const signature = Signature.fromString(testSig) 81 | assert.equal(signature.toString(), testSig) 82 | }) 83 | 84 | it('should recover pubkey from signatures', function() { 85 | const key = PrivateKey.fromString(testnetPair.private) 86 | const msg = randomBytes(32) 87 | const signature = key.sign(msg) 88 | assert.equal(signature.recover(msg).toString(), key.createPublic().toString()) 89 | }) 90 | 91 | it('should create key from login', function() { 92 | const key = PrivateKey.fromLogin('foo', 'barman') 93 | assert.equal(key.createPublic().toString(), 'STM87F7tN56tAUL2C6J9Gzi9HzgNpZdi6M2cLQo7TjDU5v178QsYA') 94 | }) 95 | 96 | it('should sign and verify transaction', function() { 97 | const tx: Transaction = { 98 | ref_block_num: 1234, 99 | ref_block_prefix: 1122334455, 100 | expiration: '2017-07-15T16:51:19', 101 | extensions: [ 102 | 'long-pants' 103 | ], 104 | operations: [ 105 | ['vote', {voter: 'foo', author: 'bar', permlink: 'baz', weight: 10000}] 106 | ] 107 | } 108 | const key = PrivateKey.fromSeed('hello') 109 | const buffer = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN) 110 | Types.Transaction(buffer, tx) 111 | buffer.flip() 112 | const data = Buffer.from(buffer.toBuffer()) 113 | const digest = createHash('sha256').update(Buffer.concat([DEFAULT_CHAIN_ID, data])).digest() 114 | const signed = cryptoUtils.signTransaction(tx, key) 115 | const pkey = key.createPublic() 116 | const sig = Signature.fromString(signed.signatures[0]) 117 | assert(pkey.verify(digest, sig)) 118 | assert.equal(sig.recover(digest).toString(), 'STM7s4VJuYFfHq8HCPpgC649Lu7CjA1V9oXgPfv8f3fszKMk3Kny9') 119 | }) 120 | 121 | it('should handle serialization errors', function() { 122 | const tx: any = { 123 | ref_block_num: 1234, 124 | ref_block_prefix: 1122334455, 125 | expiration: new Date().toISOString().slice(0, -5), 126 | extensions: [], 127 | operations: [ 128 | ['shutdown_network', {}] 129 | ] 130 | } 131 | try { 132 | cryptoUtils.signTransaction(tx, testKey) 133 | assert(false, 'should not be reached') 134 | } catch (error) { 135 | assert.equal(error.name, 'SerializationError') 136 | } 137 | }) 138 | 139 | }) 140 | -------------------------------------------------------------------------------- /test/database.ts: -------------------------------------------------------------------------------- 1 | import 'mocha' 2 | import * as assert from 'assert' 3 | 4 | import {Client, Asset, Transaction, PrivateKey} from './../src' 5 | import {getTestnetAccounts, randomString, agent, TEST_NODE} from './common' 6 | 7 | describe('database api', function() { 8 | this.slow(500) 9 | this.timeout(20 * 1000) 10 | 11 | const client = Client.testnet({agent}) 12 | let serverConfig: {[key: string]: boolean | string | number} 13 | const liveClient = new Client(TEST_NODE, {agent}) 14 | 15 | let acc: {username: string, password: string} 16 | before(async function() { 17 | [acc] = await getTestnetAccounts() 18 | }) 19 | 20 | it('getDynamicGlobalProperties', async function() { 21 | const result = await liveClient.database.getDynamicGlobalProperties() 22 | assert.deepEqual(Object.keys(result), [ 23 | 'head_block_number', 'head_block_id', 'time', 'current_witness', 24 | 'total_pow', 'num_pow_witnesses', 'virtual_supply', 'current_supply', 25 | 'confidential_supply', 'current_sbd_supply', 'confidential_sbd_supply', 26 | 'total_vesting_fund_steem', 'total_vesting_shares', 'total_reward_fund_steem', 27 | 'total_reward_shares2', 'pending_rewarded_vesting_shares', 'pending_rewarded_vesting_steem', 28 | 'sbd_interest_rate', 'sbd_print_rate', 'maximum_block_size', 'current_aslot', 29 | 'recent_slots_filled', 'participation_count', 'last_irreversible_block_num', 30 | 'vote_power_reserve_rate', 'average_block_size', 'current_reserve_ratio', 31 | 'max_virtual_bandwidth' 32 | ]) 33 | }) 34 | 35 | it('getConfig', async function() { 36 | const result = await client.database.getConfig() 37 | const r = (key: string) => result['STEEM_'+key] 38 | serverConfig = result 39 | // also test some assumptions made throughout the code 40 | const conf = await liveClient.database.getConfig() 41 | assert.equal(r('CREATE_ACCOUNT_WITH_STEEM_MODIFIER'), 30) 42 | assert.equal(r('CREATE_ACCOUNT_DELEGATION_RATIO'), 5) 43 | assert.equal(r('100_PERCENT'), 10000) 44 | assert.equal(r('1_PERCENT'), 100) 45 | 46 | const version = await client.call('database_api', 'get_version', {}) 47 | assert.equal(version['chain_id'], client.options.chainId) 48 | }) 49 | 50 | it('getBlockHeader', async function() { 51 | const result = await client.database.getBlockHeader(1) 52 | assert.equal('0000000000000000000000000000000000000000', result.previous) 53 | }) 54 | 55 | it('getBlock', async function() { 56 | const result = await client.database.getBlock(1) 57 | assert.equal('0000000000000000000000000000000000000000', result.previous) 58 | assert.equal( 59 | serverConfig['STEEM_INIT_PUBLIC_KEY_STR'], 60 | result.signing_key 61 | ) 62 | }) 63 | 64 | it('getOperations', async function() { 65 | const result = await liveClient.database.getOperations(1) 66 | assert.equal(result.length, 1) 67 | assert.equal(result[0].op[0], 'producer_reward') 68 | }) 69 | 70 | it('getDiscussions', async function() { 71 | const r1 = await liveClient.database.getDiscussions('comments', { 72 | start_author: 'almost-digital', 73 | start_permlink: 're-pal-re-almost-digital-dsteem-a-strongly-typed-steem-client-library-20170702t131034262z', 74 | tag: 'almost-digital', 75 | limit: 1, 76 | }) 77 | assert.equal(r1.length, 1) 78 | assert.equal(r1[0].body, '☀️heroin for programmers') 79 | }) 80 | 81 | it('getTransaction', async function() { 82 | const tx = await liveClient.database.getTransaction({id: 'c20a84c8a12164e1e0750f0ee5d3c37214e2f073', block_num: 13680277}) 83 | assert.deepEqual(tx.signatures, ['201e02e8daa827382b1a3aefb6809a4501eb77aa813b705be4983d50d74c66432529601e5ae43981dcba2a7e171de5fd75be2e1820942260375d2daf647df2ccaa']) 84 | try { 85 | await client.database.getTransaction({id: 'c20a84c8a12164e1e0750f0ee5d3c37214e2f073', block_num: 1}) 86 | assert(false, 'should not be reached') 87 | } catch (error) { 88 | assert.equal(error.message, 'Unable to find transaction c20a84c8a12164e1e0750f0ee5d3c37214e2f073 in block 1') 89 | } 90 | }) 91 | 92 | it('getChainProperties', async function() { 93 | const props = await liveClient.database.getChainProperties() 94 | assert.equal(Asset.from(props.account_creation_fee).symbol, 'STEEM') 95 | }) 96 | 97 | it('getCurrentMedianHistoryPrice', async function() { 98 | const price = await liveClient.database.getCurrentMedianHistoryPrice() 99 | assert.equal(Asset.from(price.base).symbol, 'SBD') 100 | assert.equal(price.quote.symbol, 'STEEM') 101 | }) 102 | 103 | it('getVestingDelegations', async function() { 104 | this.slow(5 * 1000) 105 | const [delegation] = await liveClient.database.getVestingDelegations('steem', '', 1) 106 | assert.equal(delegation.delegator, 'steem') 107 | assert.equal(typeof delegation.id, 'number') 108 | assert.equal(Asset.from(delegation.vesting_shares).symbol, 'VESTS') 109 | }) 110 | 111 | it('verifyAuthority', async function() { 112 | this.slow(5 * 1000) 113 | const tx: Transaction = { 114 | ref_block_num: 0, 115 | ref_block_prefix: 0, 116 | expiration: '2000-01-01T00:00:00', 117 | operations: [['custom_json', { 118 | required_auths: [], 119 | required_posting_auths: [acc.username], 120 | id: 'rpc-params', 121 | json: '{"foo": "bar"}' 122 | }]], 123 | 'extensions': [], 124 | } 125 | const key = PrivateKey.fromLogin(acc.username, acc.password, 'posting') 126 | const stx = client.broadcast.sign(tx, key) 127 | const rv = await client.database.verifyAuthority(stx) 128 | assert(rv === true) 129 | const bogusKey = PrivateKey.fromSeed('ogus') 130 | try { 131 | await client.database.verifyAuthority(client.broadcast.sign(tx, bogusKey)) 132 | assert(false, 'should not be reached') 133 | } catch (error) { 134 | assert.equal(error.message, `Missing Posting Authority ${ acc.username }`) 135 | } 136 | }) 137 | 138 | }) 139 | -------------------------------------------------------------------------------- /src/helpers/blockchain.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Steem blockchain helpers. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import {Client} from './../client' 37 | import {BlockHeader, SignedBlock} from './../steem/block' 38 | import {AppliedOperation} from './../steem/operation' 39 | import {iteratorStream, sleep} from './../utils' 40 | 41 | export enum BlockchainMode { 42 | /** 43 | * Only get irreversible blocks. 44 | */ 45 | Irreversible, 46 | /** 47 | * Get all blocks. 48 | */ 49 | Latest, 50 | } 51 | 52 | export interface BlockchainStreamOptions { 53 | /** 54 | * Start block number, inclusive. If omitted generation will start from current block height. 55 | */ 56 | from?: number 57 | /** 58 | * End block number, inclusive. If omitted stream will continue indefinitely. 59 | */ 60 | to?: number 61 | /** 62 | * Streaming mode, if set to `Latest` may include blocks that are not applied to the final chain. 63 | * Defaults to `Irreversible`. 64 | */ 65 | mode?: BlockchainMode 66 | } 67 | 68 | export class Blockchain { 69 | 70 | constructor(readonly client: Client) {} 71 | 72 | /** 73 | * Get latest block number. 74 | */ 75 | public async getCurrentBlockNum(mode = BlockchainMode.Irreversible) { 76 | const props = await this.client.database.getDynamicGlobalProperties() 77 | switch (mode) { 78 | case BlockchainMode.Irreversible: 79 | return props.last_irreversible_block_num 80 | case BlockchainMode.Latest: 81 | return props.head_block_number 82 | } 83 | } 84 | 85 | /** 86 | * Get latest block header. 87 | */ 88 | public async getCurrentBlockHeader(mode?: BlockchainMode) { 89 | return this.client.database.getBlockHeader(await this.getCurrentBlockNum(mode)) 90 | } 91 | 92 | /** 93 | * Get latest block. 94 | */ 95 | public async getCurrentBlock(mode?: BlockchainMode) { 96 | return this.client.database.getBlock(await this.getCurrentBlockNum(mode)) 97 | } 98 | 99 | /** 100 | * Return a asynchronous block number iterator. 101 | * @param options Feed options, can also be a block number to start from. 102 | */ 103 | public async *getBlockNumbers(options?: BlockchainStreamOptions|number) { 104 | // const config = await this.client.database.getConfig() 105 | // const interval = config['STEEMIT_BLOCK_INTERVAL'] as number 106 | const interval = 3 107 | if (!options) { 108 | options = {} 109 | } else if (typeof options === 'number') { 110 | options = {from: options} 111 | } 112 | let current = await this.getCurrentBlockNum(options.mode) 113 | if (options.from !== undefined && options.from > current) { 114 | throw new Error(`From can't be larger than current block num (${ current })`) 115 | } 116 | let seen = options.from !== undefined ? options.from : current 117 | while (true) { 118 | while (current > seen) { 119 | yield seen++ 120 | if (options.to !== undefined && seen > options.to) { 121 | return 122 | } 123 | } 124 | await sleep(interval * 1000) 125 | current = await this.getCurrentBlockNum(options.mode) 126 | } 127 | } 128 | 129 | /** 130 | * Return a stream of block numbers, accepts same parameters as {@link getBlockNumbers}. 131 | */ 132 | public getBlockNumberStream(options?: BlockchainStreamOptions|number) { 133 | return iteratorStream(this.getBlockNumbers(options)) 134 | } 135 | 136 | /** 137 | * Return a asynchronous block iterator, accepts same parameters as {@link getBlockNumbers}. 138 | */ 139 | public async *getBlocks(options?: BlockchainStreamOptions|number) { 140 | for await (const num of this.getBlockNumbers(options)) { 141 | yield await this.client.database.getBlock(num) 142 | } 143 | } 144 | 145 | /** 146 | * Return a stream of blocks, accepts same parameters as {@link getBlockNumbers}. 147 | */ 148 | public getBlockStream(options?: BlockchainStreamOptions|number) { 149 | return iteratorStream(this.getBlocks(options)) 150 | } 151 | 152 | /** 153 | * Return a asynchronous operation iterator, accepts same parameters as {@link getBlockNumbers}. 154 | */ 155 | public async *getOperations(options?: BlockchainStreamOptions|number) { 156 | for await (const num of this.getBlockNumbers(options)) { 157 | const operations = await this.client.database.getOperations(num) 158 | for (const operation of operations) { 159 | yield operation 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * Return a stream of operations, accepts same parameters as {@link getBlockNumbers}. 166 | */ 167 | public getOperationsStream(options?: BlockchainStreamOptions|number) { 168 | return iteratorStream(this.getOperations(options)) 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Misc utility functions. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import {EventEmitter} from 'events' 37 | import {PassThrough} from 'stream' 38 | import {VError} from 'verror' 39 | 40 | const fetch = global['fetch'] // tslint:disable-line:no-string-literal 41 | 42 | /** 43 | * Return a promise that will resove when a specific event is emitted. 44 | */ 45 | export function waitForEvent(emitter: EventEmitter, eventName: string|symbol): Promise { 46 | return new Promise((resolve, reject) => { 47 | emitter.once(eventName, resolve) 48 | }) 49 | } 50 | 51 | /** 52 | * Sleep for N milliseconds. 53 | */ 54 | export function sleep(ms: number): Promise { 55 | return new Promise((resolve) => { 56 | setTimeout(resolve, ms) 57 | }) 58 | } 59 | 60 | /** 61 | * Return a stream that emits iterator values. 62 | */ 63 | export function iteratorStream(iterator: AsyncIterableIterator): NodeJS.ReadableStream { 64 | const stream = new PassThrough({objectMode: true}) 65 | const iterate = async () => { 66 | for await (const item of iterator) { 67 | if (!stream.write(item)) { 68 | await waitForEvent(stream, 'drain') 69 | } 70 | } 71 | } 72 | iterate().then(() => { 73 | stream.end() 74 | }).catch((error) => { 75 | stream.emit('error', error) 76 | stream.end() 77 | }) 78 | return stream 79 | } 80 | 81 | /** 82 | * Return a deep copy of a JSON-serializable object. 83 | */ 84 | export function copy(object: T): T { 85 | return JSON.parse(JSON.stringify(object)) 86 | } 87 | 88 | /** 89 | * Fetch API wrapper that retries until timeout is reached. 90 | */ 91 | export async function retryingFetch(url: string, opts: any, timeout: number, 92 | backoff: (tries: number) => number, 93 | fetchTimeout?: (tries: number) => number) { 94 | const start = Date.now() 95 | let tries = 0 96 | do { 97 | try { 98 | if (fetchTimeout) { 99 | opts.timeout = fetchTimeout(tries) 100 | } 101 | const response = await fetch(url, opts) 102 | if (!response.ok) { 103 | throw new Error(`HTTP ${ response.status }: ${ response.statusText }`) 104 | } 105 | return await response.json() 106 | } catch (error) { 107 | if (timeout !== 0 && Date.now() - start > timeout) { 108 | throw error 109 | } 110 | await sleep(backoff(tries++)) 111 | } 112 | } while (true) 113 | } 114 | 115 | // Hack to be able to generate a valid witness_set_properties op 116 | // Can hopefully be removed when steemd's JSON representation is fixed 117 | import * as ByteBuffer from 'bytebuffer' 118 | import {PublicKey} from './crypto' 119 | import {Asset, PriceType} from './steem/asset' 120 | import {WitnessSetPropertiesOperation} from './steem/operation' 121 | import {Serializer, Types} from './steem/serializer' 122 | export interface WitnessProps { 123 | account_creation_fee?: string | Asset 124 | account_subsidy_budget?: number // uint32_t 125 | account_subsidy_decay?: number // uint32_t 126 | key: PublicKey | string 127 | maximum_block_size?: number // uint32_t 128 | new_signing_key?: PublicKey | string | null 129 | sbd_exchange_rate?: PriceType 130 | sbd_interest_rate?: number // uint16_t 131 | url?: string 132 | } 133 | function serialize(serializer: Serializer, data: any) { 134 | const buffer = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN) 135 | serializer(buffer, data) 136 | buffer.flip() 137 | return Buffer.from(buffer.toBuffer()) 138 | } 139 | export function buildWitnessUpdateOp(owner: string, props: WitnessProps): WitnessSetPropertiesOperation { 140 | const data: WitnessSetPropertiesOperation[1] = { 141 | extensions: [], owner, props: [] 142 | } 143 | for (const key of Object.keys(props)) { 144 | let type: Serializer 145 | switch (key) { 146 | case 'key': 147 | case 'new_signing_key': 148 | type = Types.PublicKey 149 | break 150 | case 'account_subsidy_budget': 151 | case 'account_subsidy_decay': 152 | case 'maximum_block_size': 153 | type = Types.UInt32 154 | break 155 | case 'sbd_interest_rate': 156 | type = Types.UInt16 157 | break 158 | case 'url': 159 | type = Types.String 160 | break 161 | case 'sbd_exchange_rate': 162 | type = Types.Price 163 | break 164 | case 'account_creation_fee': 165 | type = Types.Asset 166 | break 167 | default: 168 | throw new Error(`Unknown witness prop: ${ key }`) 169 | } 170 | data.props.push([key, serialize(type, props[key])]) 171 | } 172 | data.props.sort((a, b) => a[0].localeCompare(b[0])) 173 | return ['witness_set_properties', data] 174 | } 175 | -------------------------------------------------------------------------------- /src/steem/account.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Steem account type definitions. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import * as ByteBuffer from 'bytebuffer' 37 | 38 | import {PublicKey} from './../crypto' 39 | import {Asset} from './asset' 40 | 41 | export interface AuthorityType { 42 | weight_threshold: number // uint32_t 43 | account_auths: Array<[string, number]> // flat_map< account_name_type, uint16_t > 44 | key_auths: Array<[string | PublicKey, number]>// flat_map< public_key_type, uint16_t > 45 | } 46 | 47 | export class Authority implements AuthorityType { 48 | 49 | /** 50 | * Convenience to create a new instance from PublicKey or authority object. 51 | */ 52 | public static from(value: string | PublicKey | AuthorityType) { 53 | if (value instanceof Authority) { 54 | return value 55 | } else if (typeof value === 'string' || value instanceof PublicKey) { 56 | return new Authority({ 57 | account_auths: [], 58 | key_auths: [[value, 1]], 59 | weight_threshold: 1, 60 | }) 61 | } else { 62 | return new Authority(value) 63 | } 64 | } 65 | 66 | public weight_threshold: number 67 | public account_auths: Array<[string, number]> 68 | public key_auths: Array<[string | PublicKey, number]> 69 | 70 | constructor({weight_threshold, account_auths, key_auths}: AuthorityType) { 71 | this.weight_threshold = weight_threshold 72 | this.account_auths = account_auths 73 | this.key_auths = key_auths 74 | } 75 | } 76 | 77 | export interface Account { 78 | id: number // account_id_type 79 | name: string // account_name_type 80 | owner: Authority 81 | active: Authority 82 | posting: Authority 83 | memo_key: string // public_key_type 84 | json_metadata: string 85 | proxy: string // account_name_type 86 | last_owner_update: string // time_point_sec 87 | last_account_update: string // time_point_sec 88 | created: string // time_point_sec 89 | mined: boolean 90 | owner_challenged: boolean 91 | active_challenged: boolean 92 | last_owner_proved: string // time_point_sec 93 | last_active_proved: string // time_point_sec 94 | recovery_account: string // account_name_type 95 | reset_account: string // account_name_type 96 | last_account_recovery: string // time_point_sec 97 | comment_count: number // uint32_t 98 | lifetime_vote_count: number // uint32_t 99 | post_count: number // uint32_t 100 | can_vote: boolean 101 | voting_power: number // uint16_t 102 | last_vote_time: string // time_point_sec 103 | voting_manabar: { 104 | current_mana: string | number, 105 | last_update_time: number 106 | }, 107 | balance: string | Asset 108 | savings_balance: string | Asset 109 | sbd_balance: string | Asset 110 | sbd_seconds: string // uint128_t 111 | sbd_seconds_last_update: string // time_point_sec 112 | sbd_last_interest_payment: string // time_point_sec 113 | savings_sbd_balance: string | Asset // asset 114 | savings_sbd_seconds: string // uint128_t 115 | savings_sbd_seconds_last_update: string // time_point_sec 116 | savings_sbd_last_interest_payment: string // time_point_sec 117 | savings_withdraw_requests: number // uint8_t 118 | reward_sbd_balance: string | Asset 119 | reward_steem_balance: string | Asset 120 | reward_vesting_balance: string | Asset 121 | reward_vesting_steem: string | Asset 122 | curation_rewards: number | string // share_type 123 | posting_rewards: number | string // share_type 124 | vesting_shares: string | Asset 125 | delegated_vesting_shares: string | Asset 126 | received_vesting_shares: string | Asset 127 | vesting_withdraw_rate: string | Asset 128 | next_vesting_withdrawal: string // time_point_sec 129 | withdrawn: number | string // share_type 130 | to_withdraw: number | string // share_type 131 | withdraw_routes: number // uint16_t 132 | proxied_vsf_votes: number[] // vector< share_type > 133 | witnesses_voted_for: number // uint16_t 134 | average_bandwidth: number | string // share_type 135 | lifetime_bandwidth: number | string // share_type 136 | last_bandwidth_update: string // time_point_sec 137 | average_market_bandwidth: number | string // share_type 138 | lifetime_market_bandwidth: number | string // share_type 139 | last_market_bandwidth_update: string // time_point_sec 140 | last_post: string // time_point_sec 141 | last_root_post: string // time_point_sec 142 | } 143 | 144 | export interface ExtendedAccount extends Account { 145 | /** 146 | * Convert vesting_shares to vesting steem. 147 | */ 148 | vesting_balance: string | Asset 149 | reputation: string | number // share_type 150 | /** 151 | * Transfer to/from vesting. 152 | */ 153 | transfer_history: any[] // map 154 | /** 155 | * Limit order / cancel / fill. 156 | */ 157 | market_history: any[] // map 158 | post_history: any[] // map 159 | vote_history: any[] // map 160 | other_history: any[] // map 161 | witness_votes: string[] // set 162 | tags_usage: string[] // vector> 163 | guest_bloggers: string[] // vector> 164 | open_orders?: any[] // optional> 165 | comments?: any[] /// permlinks for this user // optional> 166 | blog?: any[] /// blog posts for this user // optional> 167 | feed?: any[] /// feed posts for this user // optional> 168 | recent_replies?: any[] /// blog posts for this user // optional> 169 | recommended?: any[] /// posts recommened for this user // optional> 170 | } 171 | -------------------------------------------------------------------------------- /src/helpers/database.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Database API helpers. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import {Client} from './../client' 37 | import {ExtendedAccount} from './../steem/account' 38 | import {Asset, Price} from './../steem/asset' 39 | import {BlockHeader, SignedBlock} from './../steem/block' 40 | import {Discussion} from './../steem/comment' 41 | import {DynamicGlobalProperties} from './../steem/misc' 42 | import {ChainProperties, VestingDelegation} from './../steem/misc' 43 | import {AppliedOperation} from './../steem/operation' 44 | import {SignedTransaction, Transaction, TransactionConfirmation} from './../steem/transaction' 45 | 46 | /** 47 | * Possible categories for `get_discussions_by_*`. 48 | */ 49 | export type DiscussionQueryCategory = 'active' | 'blog' | 'cashout' | 'children' | 'comments' | 50 | 'feed' | 'hot' | 'promoted' | 'trending' | 'votes' | 'created' 51 | 52 | export interface DisqussionQuery { 53 | /** 54 | * Name of author or tag to fetch. 55 | */ 56 | tag: string 57 | /** 58 | * Number of results, max 100. 59 | */ 60 | limit: number 61 | filter_tags?: string[] 62 | select_authors?: string[] 63 | select_tags?: string[] 64 | /** 65 | * Number of bytes of post body to fetch, default 0 (all) 66 | */ 67 | truncate_body?: number 68 | /** 69 | * Name of author to start from, used for paging. 70 | * Should be used in conjunction with `start_permlink`. 71 | */ 72 | start_author?: string 73 | /** 74 | * Permalink of post to start from, used for paging. 75 | * Should be used in conjunction with `start_author`. 76 | */ 77 | start_permlink?: string 78 | parent_author?: string 79 | parent_permlink?: string 80 | } 81 | 82 | export class DatabaseAPI { 83 | 84 | constructor(readonly client: Client) {} 85 | 86 | /** 87 | * Convenience for calling `database_api`. 88 | */ 89 | public call(method: string, params?: any[]) { 90 | return this.client.call('condenser_api', method, params) 91 | } 92 | 93 | /** 94 | * Return state of server. 95 | */ 96 | public getDynamicGlobalProperties(): Promise { 97 | return this.call('get_dynamic_global_properties') 98 | } 99 | 100 | /** 101 | * Return median chain properties decided by witness. 102 | */ 103 | public async getChainProperties(): Promise { 104 | return this.call('get_chain_properties') 105 | } 106 | 107 | /** 108 | * Return all of the state required for a particular url path. 109 | * @param path Path component of url conforming to condenser's scheme 110 | * e.g. `@almost-digital` or `trending/travel` 111 | */ 112 | public async getState(path: string): Promise { 113 | return this.call('get_state', [path]) 114 | } 115 | 116 | /** 117 | * Return median price in SBD for 1 STEEM as reported by the witnesses. 118 | */ 119 | public async getCurrentMedianHistoryPrice(): Promise { 120 | return Price.from(await this.call('get_current_median_history_price')) 121 | } 122 | 123 | /** 124 | * Get list of delegations made by account. 125 | * @param account Account delegating 126 | * @param from Delegatee start offset, used for paging. 127 | * @param limit Number of results, max 1000. 128 | */ 129 | public async getVestingDelegations(account: string, 130 | from: string = '', 131 | limit: number = 1000): Promise { 132 | return this.call('get_vesting_delegations', [account, from, limit]) 133 | } 134 | 135 | /** 136 | * Return server config. See: 137 | * https://github.com/steemit/steem/blob/master/libraries/protocol/include/steemit/protocol/config.hpp 138 | */ 139 | public getConfig(): Promise<{[name: string]: string|number|boolean}> { 140 | return this.call('get_config') 141 | } 142 | 143 | /** 144 | * Return header for *blockNum*. 145 | */ 146 | public getBlockHeader(blockNum: number): Promise { 147 | return this.call('get_block_header', [blockNum]) 148 | } 149 | 150 | /** 151 | * Return block *blockNum*. 152 | */ 153 | public getBlock(blockNum: number): Promise { 154 | return this.call('get_block', [blockNum]) 155 | } 156 | 157 | /** 158 | * Return all applied operations in *blockNum*. 159 | */ 160 | public getOperations(blockNum: number, onlyVirtual: boolean = false): Promise { 161 | return this.call('get_ops_in_block', [blockNum, onlyVirtual]) 162 | } 163 | 164 | /** 165 | * Return array of discussions (a.k.a. posts). 166 | * @param by The type of sorting for the discussions, valid options are: 167 | * `active` `blog` `cashout` `children` `comments` `created` 168 | * `feed` `hot` `promoted` `trending` `votes`. Note that 169 | * for `blog` and `feed` the tag is set to a username. 170 | */ 171 | public getDiscussions(by: DiscussionQueryCategory, query: DisqussionQuery): Promise { 172 | return this.call(`get_discussions_by_${ by }`, [query]) 173 | } 174 | 175 | /** 176 | * Return array of account info objects for the usernames passed. 177 | * @param usernames The accounts to fetch. 178 | */ 179 | public getAccounts(usernames: string[]): Promise { 180 | return this.call('get_accounts', [usernames]) 181 | } 182 | 183 | /** 184 | * Convenience to fetch a block and return a specific transaction. 185 | */ 186 | public async getTransaction(txc: TransactionConfirmation | {block_num: number, id: string}) { 187 | const block = await this.client.database.getBlock(txc.block_num) 188 | const idx = block.transaction_ids.indexOf(txc.id) 189 | if (idx === -1) { 190 | throw new Error(`Unable to find transaction ${ txc.id } in block ${ txc.block_num }`) 191 | } 192 | return block.transactions[idx] as SignedTransaction 193 | } 194 | 195 | /** 196 | * Verify signed transaction. 197 | */ 198 | public async verifyAuthority(stx: SignedTransaction): Promise { 199 | return this.call('verify_authority', [stx]) 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /test/_karma-sauce.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | 3 | // https://wiki.saucelabs.com/display/DOCS/Platform+Configurator#/ 4 | const customLaunchers = {} 5 | const addLauncher = (fn) => { 6 | const c = {} 7 | const caps = fn(c) || c 8 | const name = Object.keys(caps) 9 | .map((k)=>caps[k]).filter((v)=>typeof v==='string') 10 | .join('_').toLowerCase().replace(/\W+/g, '') 11 | customLaunchers[name] = caps 12 | customLaunchers[name].base = 'SauceLabs' 13 | } 14 | 15 | const shuffle = (array) => { 16 | let currentIndex = array.length, temporaryValue, randomIndex 17 | while (0 !== currentIndex) { 18 | randomIndex = Math.floor(Math.random() * currentIndex) 19 | currentIndex -= 1 20 | temporaryValue = array[currentIndex] 21 | array[currentIndex] = array[randomIndex] 22 | array[randomIndex] = temporaryValue 23 | } 24 | return array 25 | } 26 | 27 | addLauncher((caps) => { 28 | caps['browserName'] = 'Safari'; 29 | caps['appiumVersion'] = '1.6.4'; 30 | caps['deviceName'] = 'iPhone Simulator'; 31 | caps['deviceOrientation'] = 'portrait'; 32 | caps['platformVersion'] = '10.3'; 33 | caps['platformName'] = 'iOS'; 34 | }) 35 | 36 | addLauncher((caps) => { 37 | caps['browserName'] = 'Safari'; 38 | caps['appiumVersion'] = '1.6.4'; 39 | caps['deviceName'] = 'iPhone Simulator'; 40 | caps['deviceOrientation'] = 'portrait'; 41 | caps['platformVersion'] = '9.3'; 42 | caps['platformName'] = 'iOS'; 43 | }) 44 | 45 | addLauncher((caps) => { 46 | caps['browserName'] = 'Browser'; 47 | caps['appiumVersion'] = '1.6.4'; 48 | caps['deviceName'] = 'Samsung Galaxy S4 GoogleAPI Emulator'; 49 | caps['deviceOrientation'] = 'portrait'; 50 | caps['platformVersion'] = '4.4'; 51 | caps['platformName'] = 'Android'; 52 | }) 53 | 54 | addLauncher((caps) => { 55 | caps['browserName'] = 'Android'; 56 | caps['appiumVersion'] = '1.6.4'; 57 | caps['deviceName'] = 'Android GoogleAPI Emulator'; 58 | caps['deviceOrientation'] = 'portrait'; 59 | caps['browserName'] = 'Chrome'; 60 | caps['platformVersion'] = '7.1'; 61 | caps['platformName'] = 'Android'; 62 | }) 63 | 64 | 65 | addLauncher((caps) => { 66 | caps['browserName'] = 'Android'; 67 | caps['appiumVersion'] = '1.6.4'; 68 | caps['deviceName'] = 'Android Emulator'; 69 | caps['deviceOrientation'] = 'portrait'; 70 | caps['browserName'] = 'Chrome'; 71 | caps['platformVersion'] = '6.0'; 72 | caps['platformName'] = 'Android'; 73 | }) 74 | 75 | addLauncher((caps) => { 76 | caps['browserName'] = 'Browser'; 77 | caps['appiumVersion'] = '1.6.4'; 78 | caps['deviceName'] = 'Android Emulator'; 79 | caps['deviceOrientation'] = 'portrait'; 80 | caps['platformVersion'] = '5.1'; 81 | caps['platformName'] = 'Android'; 82 | }) 83 | 84 | addLauncher((caps) => { 85 | caps = {browserName: 'chrome'}; 86 | caps['platform'] = 'Linux'; 87 | caps['version'] = '48.0'; 88 | return caps 89 | }) 90 | 91 | addLauncher((caps) => { 92 | caps = {browserName: 'chrome'}; 93 | caps['platform'] = 'Windows 7'; 94 | caps['version'] = '48.0'; 95 | return caps 96 | }) 97 | 98 | addLauncher((caps) => { 99 | caps = {browserName: 'firefox'}; 100 | caps['platform'] = 'Windows 10'; 101 | caps['version'] = '55.0'; 102 | return caps 103 | }) 104 | 105 | addLauncher((caps) => { 106 | caps = {browserName: 'chrome'}; 107 | caps['platform'] = 'Windows 10'; 108 | caps['version'] = '59.0'; 109 | return caps 110 | }) 111 | 112 | addLauncher((caps) => { 113 | caps = {browserName: 'chrome'}; 114 | caps['platform'] = 'macOS 10.12'; 115 | caps['version'] = '48.0'; 116 | return caps 117 | }) 118 | 119 | addLauncher((caps) => { 120 | caps = {browserName: 'safari'}; 121 | caps['platform'] = 'macOS 10.12'; 122 | caps['version'] = '10.0'; 123 | return caps 124 | }) 125 | 126 | addLauncher((caps) => { 127 | caps = {browserName: 'safari'}; 128 | caps['platform'] = 'OS X 10.11'; 129 | caps['version'] = '9.0'; 130 | return caps 131 | }) 132 | 133 | addLauncher((caps) => { 134 | caps = {browserName: 'safari'}; 135 | caps['platform'] = 'OS X 10.10'; 136 | caps['version'] = '8.0'; 137 | return caps 138 | }) 139 | 140 | addLauncher((caps) => { 141 | caps = {browserName: 'firefox'}; 142 | caps['platform'] = 'macOS 10.12'; 143 | caps['version'] = '54.0'; 144 | return caps 145 | }) 146 | 147 | addLauncher((caps) => { 148 | caps = {browserName: 'firefox'}; 149 | caps['platform'] = 'Linux'; 150 | caps['version'] = '45.0'; 151 | return caps 152 | }) 153 | 154 | addLauncher((caps) => { 155 | caps = {browserName: 'internet explorer'}; 156 | caps['platform'] = 'Windows 10'; 157 | caps['version'] = '11.103'; 158 | return caps 159 | }) 160 | 161 | addLauncher((caps) => { 162 | caps = {browserName: 'MicrosoftEdge'}; 163 | caps['platform'] = 'Windows 10'; 164 | caps['version'] = '15.15063'; 165 | return caps 166 | }) 167 | 168 | addLauncher((caps) => { 169 | caps = {browserName: 'MicrosoftEdge'}; 170 | caps['platform'] = 'Windows 10'; 171 | caps['version'] = '14.14393'; 172 | return caps 173 | }) 174 | 175 | addLauncher((caps) => { 176 | caps = {browserName: 'firefox'}; 177 | caps['platform'] = 'Windows 7'; 178 | caps['version'] = '54.0'; 179 | return caps 180 | }) 181 | 182 | addLauncher((caps) => { 183 | caps = {browserName: 'chrome'}; 184 | caps['platform'] = 'Windows 8.1'; 185 | caps['version'] = '32.0'; 186 | return caps 187 | }) 188 | 189 | addLauncher((caps) => { 190 | caps = {browserName: 'firefox'}; 191 | caps['platform'] = 'Windows 8.1'; 192 | caps['version'] = '35.0'; 193 | return caps 194 | }) 195 | 196 | addLauncher((caps) => { 197 | caps['browserName'] = 'Safari'; 198 | caps['appiumVersion'] = '1.6.4'; 199 | caps['deviceName'] = 'iPhone 7 Simulator'; 200 | caps['deviceOrientation'] = 'portrait'; 201 | caps['platformVersion'] = '10.3'; 202 | caps['platformName'] = 'iOS'; 203 | }) 204 | 205 | addLauncher((caps) => { 206 | caps['browserName'] = 'Safari'; 207 | caps['appiumVersion'] = '1.6.4'; 208 | caps['deviceName'] = 'iPhone 5 Simulator'; 209 | caps['deviceOrientation'] = 'portrait'; 210 | caps['platformVersion'] = '8.4'; 211 | caps['platformName'] = 'iOS'; 212 | }) 213 | 214 | addLauncher((caps) => { 215 | caps = {browserName: 'chrome'}; 216 | caps['platform'] = 'Windows 8.1'; 217 | caps['version'] = '60.0'; 218 | return caps 219 | }) 220 | 221 | addLauncher((caps) => { 222 | caps['browserName'] = 'Safari'; 223 | caps['appiumVersion'] = '1.8.1'; 224 | caps['deviceName'] = 'iPhone X Simulator'; 225 | caps['deviceOrientation'] = 'portrait'; 226 | caps['platformVersion'] = '11.3'; 227 | caps['platformName'] = 'iOS'; 228 | }) 229 | 230 | addLauncher((caps) => { 231 | caps['browserName'] = 'internet explorer'; 232 | caps['platform'] = 'Windows 7'; 233 | caps['version'] = '10.0'; 234 | }) 235 | 236 | addLauncher((caps) => { 237 | caps['browserName'] = 'MicrosoftEdge'; 238 | caps['platform'] = 'Windows 10'; 239 | caps['version'] = '16.16299'; 240 | }) 241 | 242 | addLauncher((caps) => { 243 | caps['browserName'] = 'chrome'; 244 | caps['platform'] = 'Windows 10'; 245 | caps['version'] = '67.0'; 246 | }) 247 | 248 | addLauncher((caps) => { 249 | caps['browserName'] = 'safari'; 250 | caps['platform'] = 'macOS 10.13'; 251 | caps['version'] = '11.1'; 252 | }) 253 | 254 | addLauncher((caps) => { 255 | caps['browserName'] = 'safari'; 256 | caps['platform'] = 'macOS 10.12'; 257 | caps['version'] = '10.1'; 258 | }) 259 | 260 | addLauncher((caps) => { 261 | caps['browserName'] = 'firefox'; 262 | caps['platform'] = 'macOS 10.13'; 263 | caps['version'] = '60.0'; 264 | }) 265 | 266 | config.set({ 267 | frameworks: ['browserify', 'mocha'], 268 | files: ['_browser.js'], 269 | preprocessors: {'_browser.js': ['browserify' ]}, 270 | browserify: { 271 | debug: true, 272 | plugin: [['tsify', {project: 'test/tsconfig.json'}]] 273 | }, 274 | colors: true, 275 | logLevel: config.LOG_INFO, 276 | captureTimeout: 0, 277 | browserNoActivityTimeout: 1000 * 60 * 5, 278 | sauceLabs: { 279 | testName: 'jnordberg/dsteem', 280 | connectOptions: {tunnelDomains: 'localhost,127.0.0.1'} 281 | }, 282 | concurrency: 5, 283 | customLaunchers: customLaunchers, 284 | browsers: shuffle(Object.keys(customLaunchers)), 285 | reporters: ['mocha', 'saucelabs'], 286 | singleRun: true 287 | }) 288 | } 289 | -------------------------------------------------------------------------------- /src/steem/asset.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Steem asset type definitions and helpers. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import * as assert from 'assert' 37 | import * as ByteBuffer from 'bytebuffer' 38 | 39 | export interface SMTAsset { 40 | amount: string|number, 41 | precision: number, 42 | nai: string 43 | } 44 | 45 | /** 46 | * Asset symbol string. 47 | */ 48 | export type AssetSymbol = 'STEEM' | 'VESTS' | 'SBD' | 'TESTS' | 'TBD' 49 | 50 | /** 51 | * Class representing a steem asset, e.g. `1.000 STEEM` or `12.112233 VESTS`. 52 | */ 53 | export class Asset { 54 | 55 | /** 56 | * Create a new Asset instance from a string, e.g. `42.000 STEEM`. 57 | */ 58 | public static fromString(string: string, expectedSymbol?: AssetSymbol) { 59 | const [amountString, symbol] = string.split(' ') 60 | if (['STEEM', 'VESTS', 'SBD', 'TESTS', 'TBD'].indexOf(symbol) === -1) { 61 | throw new Error(`Invalid asset symbol: ${ symbol }`) 62 | } 63 | if (expectedSymbol && symbol !== expectedSymbol) { 64 | throw new Error(`Invalid asset, expected symbol: ${ expectedSymbol } got: ${ symbol }`) 65 | } 66 | const amount = Number.parseFloat(amountString) 67 | if (!Number.isFinite(amount)) { 68 | throw new Error(`Invalid asset amount: ${ amountString }`) 69 | } 70 | return new Asset(amount, symbol as AssetSymbol) 71 | } 72 | 73 | /** 74 | * Convenience to create new Asset. 75 | * @param symbol Symbol to use when created from number. Will also be used to validate 76 | * the asset, throws if the passed value has a different symbol than this. 77 | */ 78 | public static from(value: string | Asset | number, symbol?: AssetSymbol) { 79 | if (value instanceof Asset) { 80 | if (symbol && value.symbol !== symbol) { 81 | throw new Error(`Invalid asset, expected symbol: ${ symbol } got: ${ value.symbol }`) 82 | } 83 | return value 84 | } else if (typeof value === 'number' && Number.isFinite(value)) { 85 | return new Asset(value, symbol || 'STEEM') 86 | } else if (typeof value === 'string') { 87 | return Asset.fromString(value, symbol) 88 | } else { 89 | throw new Error(`Invalid asset '${ String(value) }'`) 90 | } 91 | } 92 | 93 | /** 94 | * Return the smaller of the two assets. 95 | */ 96 | public static min(a: Asset, b: Asset) { 97 | assert(a.symbol === b.symbol, 'can not compare assets with different symbols') 98 | return a.amount < b.amount ? a : b 99 | } 100 | 101 | /** 102 | * Return the larger of the two assets. 103 | */ 104 | public static max(a: Asset, b: Asset) { 105 | assert(a.symbol === b.symbol, 'can not compare assets with different symbols') 106 | return a.amount > b.amount ? a : b 107 | } 108 | 109 | constructor(public readonly amount: number, public readonly symbol: AssetSymbol) {} 110 | 111 | /** 112 | * Return asset precision. 113 | */ 114 | public getPrecision(): number { 115 | switch (this.symbol) { 116 | case 'TESTS': 117 | case 'TBD': 118 | case 'STEEM': 119 | case 'SBD': 120 | return 3 121 | case 'VESTS': 122 | return 6 123 | } 124 | } 125 | 126 | /** 127 | * Return a string representation of this asset, e.g. `42.000 STEEM`. 128 | */ 129 | public toString(): string { 130 | return `${ this.amount.toFixed(this.getPrecision()) } ${ this.symbol }` 131 | } 132 | 133 | /** 134 | * Return a new Asset instance with amount added. 135 | */ 136 | public add(amount: Asset | string | number): Asset { 137 | const other = Asset.from(amount, this.symbol) 138 | assert(this.symbol === other.symbol, 'can not add with different symbols') 139 | return new Asset(this.amount + other.amount, this.symbol) 140 | } 141 | 142 | /** 143 | * Return a new Asset instance with amount subtracted. 144 | */ 145 | public subtract(amount: Asset | string | number): Asset { 146 | const other = Asset.from(amount, this.symbol) 147 | assert(this.symbol === other.symbol, 'can not subtract with different symbols') 148 | return new Asset(this.amount - other.amount, this.symbol) 149 | } 150 | 151 | /** 152 | * Return a new Asset with the amount multiplied by factor. 153 | */ 154 | public multiply(factor: Asset | string | number): Asset { 155 | const other = Asset.from(factor, this.symbol) 156 | assert(this.symbol === other.symbol, 'can not multiply with different symbols') 157 | return new Asset(this.amount * other.amount, this.symbol) 158 | } 159 | 160 | /** 161 | * Return a new Asset with the amount divided. 162 | */ 163 | public divide(divisor: Asset | string | number): Asset { 164 | const other = Asset.from(divisor, this.symbol) 165 | assert(this.symbol === other.symbol, 'can not divide with different symbols') 166 | return new Asset(this.amount / other.amount, this.symbol) 167 | } 168 | 169 | /** 170 | * For JSON serialization, same as toString(). 171 | */ 172 | public toJSON(): string { 173 | return this.toString() 174 | } 175 | 176 | } 177 | 178 | export type PriceType = Price | {base: Asset | string, quote: Asset | string} 179 | 180 | /** 181 | * Represents quotation of the relative value of asset against another asset. 182 | * Similar to 'currency pair' used to determine value of currencies. 183 | * 184 | * For example: 185 | * 1 EUR / 1.25 USD where: 186 | * 1 EUR is an asset specified as a base 187 | * 1.25 USD us an asset specified as a qute 188 | * 189 | * can determine value of EUR against USD. 190 | */ 191 | export class Price { 192 | 193 | /** 194 | * Convenience to create new Price. 195 | */ 196 | public static from(value: PriceType) { 197 | if (value instanceof Price) { 198 | return value 199 | } else { 200 | return new Price(Asset.from(value.base), Asset.from(value.quote)) 201 | } 202 | } 203 | 204 | /** 205 | * @param base - represents a value of the price object to be expressed relatively to quote 206 | * asset. Cannot have amount == 0 if you want to build valid price. 207 | * @param quote - represents an relative asset. Cannot have amount == 0, otherwise 208 | * asertion fail. 209 | * 210 | * Both base and quote shall have different symbol defined. 211 | */ 212 | constructor(public readonly base: Asset, public readonly quote: Asset) { 213 | assert(base.amount !== 0 && quote.amount !== 0, 'base and quote assets must be non-zero') 214 | assert(base.symbol !== quote.symbol, 'base and quote can not have the same symbol') 215 | } 216 | 217 | /** 218 | * Return a string representation of this price pair. 219 | */ 220 | public toString() { 221 | return `${ this.base }:${ this.quote }` 222 | } 223 | 224 | /** 225 | * Return a new Asset with the price converted between the symbols in the pair. 226 | * Throws if passed asset symbol is not base or quote. 227 | */ 228 | public convert(asset: Asset) { 229 | if (asset.symbol === this.base.symbol) { 230 | assert(this.base.amount > 0) 231 | return new Asset(asset.amount * this.quote.amount / this.base.amount, this.quote.symbol) 232 | } else if (asset.symbol === this.quote.symbol) { 233 | assert(this.quote.amount > 0) 234 | return new Asset(asset.amount * this.base.amount / this.quote.amount, this.base.symbol) 235 | } else { 236 | throw new Error(`Can not convert ${ asset } with ${ this }`) 237 | } 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /docs/interfaces/pool.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pool | dsteem 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

Interface Pool

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Hierarchy

71 |
    72 |
  • 73 | Pool 74 |
  • 75 |
76 |
77 |
78 |

Index

79 |
80 |
81 |
82 |

Properties

83 | 86 |
87 |
88 |
89 |
90 |
91 |

Properties

92 |
93 | 94 |

pool

95 |
pool: Bignum
96 | 101 |
102 |
103 |
104 | 129 |
130 |
131 |
132 |
133 |

Legend

134 |
135 |
    136 |
  • Module
  • 137 |
  • Object literal
  • 138 |
  • Variable
  • 139 |
  • Function
  • 140 |
  • Function with type parameter
  • 141 |
  • Index signature
  • 142 |
  • Type alias
  • 143 |
144 |
    145 |
  • Enumeration
  • 146 |
  • Enumeration member
  • 147 |
  • Property
  • 148 |
  • Method
  • 149 |
150 |
    151 |
  • Interface
  • 152 |
  • Interface with type parameter
  • 153 |
  • Constructor
  • 154 |
  • Property
  • 155 |
  • Method
  • 156 |
  • Index signature
  • 157 |
158 |
    159 |
  • Class
  • 160 |
  • Class with type parameter
  • 161 |
  • Constructor
  • 162 |
  • Property
  • 163 |
  • Method
  • 164 |
  • Accessor
  • 165 |
  • Index signature
  • 166 |
167 |
    168 |
  • Inherited constructor
  • 169 |
  • Inherited property
  • 170 |
  • Inherited method
  • 171 |
  • Inherited accessor
  • 172 |
173 |
    174 |
  • Protected property
  • 175 |
  • Protected method
  • 176 |
  • Protected accessor
  • 177 |
178 |
    179 |
  • Private property
  • 180 |
  • Private method
  • 181 |
  • Private accessor
  • 182 |
183 |
    184 |
  • Static property
  • 185 |
  • Static method
  • 186 |
187 |
188 |
189 |
190 |
191 |

Generated using TypeDoc

192 |
193 |
194 | 195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /src/steem/misc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Misc steem type definitions. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | import { Account } from './account' 36 | import {Asset, Price} from './asset' 37 | 38 | /** 39 | * Large number that may be unsafe to represent natively in JavaScript. 40 | */ 41 | export type Bignum = string 42 | 43 | /** 44 | * Buffer wrapper that serializes to a hex-encoded string. 45 | */ 46 | export class HexBuffer { 47 | 48 | /** 49 | * Convenience to create a new HexBuffer, does not copy data if value passed is already a buffer. 50 | */ 51 | public static from(value: Buffer | HexBuffer | number[] | string) { 52 | if (value instanceof HexBuffer) { 53 | return value 54 | } else if (value instanceof Buffer) { 55 | return new HexBuffer(value) 56 | } else if (typeof value === 'string') { 57 | return new HexBuffer(Buffer.from(value, 'hex')) 58 | } else { 59 | return new HexBuffer(Buffer.from(value)) 60 | } 61 | } 62 | 63 | constructor(public buffer: Buffer) {} 64 | 65 | public toString(encoding = 'hex') { 66 | return this.buffer.toString(encoding) 67 | } 68 | 69 | public toJSON() { 70 | return this.toString() 71 | } 72 | 73 | } 74 | 75 | /** 76 | * Chain roperties that are decided by the witnesses. 77 | */ 78 | export interface ChainProperties { 79 | /** 80 | * This fee, paid in STEEM, is converted into VESTING SHARES for the new account. Accounts 81 | * without vesting shares cannot earn usage rations and therefore are powerless. This minimum 82 | * fee requires all accounts to have some kind of commitment to the network that includes the 83 | * ability to vote and make transactions. 84 | * 85 | * @note This has to be multiplied by `STEEMIT_CREATE_ACCOUNT_WITH_STEEM_MODIFIER` 86 | * (defined as 30 on the main chain) to get the minimum fee needed to create an account. 87 | * 88 | */ 89 | account_creation_fee: string | Asset 90 | /** 91 | * This witnesses vote for the maximum_block_size which is used by the network 92 | * to tune rate limiting and capacity. 93 | */ 94 | maximum_block_size: number // uint32_t 95 | /** 96 | * The SBD interest percentage rate decided by witnesses, expressed 0 to 10000. 97 | */ 98 | sbd_interest_rate: number // uint16_t STEEMIT_100_PERCENT 99 | } 100 | 101 | export interface VestingDelegation { 102 | /** 103 | * Delegation id. 104 | */ 105 | id: number // id_type 106 | /** 107 | * Account that is delegating vests to delegatee. 108 | */ 109 | delegator: string // account_name_type 110 | /** 111 | * Account that is receiving vests from delegator. 112 | */ 113 | delegatee: string // account_name_type 114 | /** 115 | * Amount of VESTS delegated. 116 | */ 117 | vesting_shares: Asset | string 118 | /** 119 | * Earliest date delegation can be removed. 120 | */ 121 | min_delegation_time: string // time_point_sec 122 | } 123 | 124 | /** 125 | * Node state. 126 | */ 127 | export interface DynamicGlobalProperties { 128 | id: number 129 | /** 130 | * Current block height. 131 | */ 132 | head_block_number: number 133 | head_block_id: string 134 | /** 135 | * UTC Server time, e.g. 2020-01-15T00:42:00 136 | */ 137 | time: string 138 | /** 139 | * Currently elected witness. 140 | */ 141 | current_witness: string 142 | /** 143 | * The total POW accumulated, aka the sum of num_pow_witness at the time 144 | * new POW is added. 145 | */ 146 | total_pow: number 147 | /** 148 | * The current count of how many pending POW witnesses there are, determines 149 | * the difficulty of doing pow. 150 | */ 151 | num_pow_witnesses: number 152 | virtual_supply: Asset | string 153 | current_supply: Asset | string 154 | /** 155 | * Total asset held in confidential balances. 156 | */ 157 | confidential_supply: Asset | string 158 | current_sbd_supply: Asset | string 159 | /** 160 | * Total asset held in confidential balances. 161 | */ 162 | confidential_sbd_supply: Asset | string 163 | total_vesting_fund_steem: Asset | string 164 | total_vesting_shares: Asset | string 165 | total_reward_fund_steem: Asset | string 166 | /** 167 | * The running total of REWARD^2. 168 | */ 169 | total_reward_shares2: string 170 | pending_rewarded_vesting_shares: Asset | string 171 | pending_rewarded_vesting_steem: Asset | string 172 | /** 173 | * This property defines the interest rate that SBD deposits receive. 174 | */ 175 | sbd_interest_rate: number 176 | sbd_print_rate: number 177 | /** 178 | * Average block size is updated every block to be: 179 | * 180 | * average_block_size = (99 * average_block_size + new_block_size) / 100 181 | * 182 | * This property is used to update the current_reserve_ratio to maintain 183 | * approximately 50% or less utilization of network capacity. 184 | */ 185 | average_block_size: number 186 | /** 187 | * Maximum block size is decided by the set of active witnesses which change every round. 188 | * Each witness posts what they think the maximum size should be as part of their witness 189 | * properties, the median size is chosen to be the maximum block size for the round. 190 | * 191 | * @note the minimum value for maximum_block_size is defined by the protocol to prevent the 192 | * network from getting stuck by witnesses attempting to set this too low. 193 | */ 194 | maximum_block_size: number 195 | /** 196 | * The current absolute slot number. Equal to the total 197 | * number of slots since genesis. Also equal to the total 198 | * number of missed slots plus head_block_number. 199 | */ 200 | current_aslot: number 201 | /** 202 | * Used to compute witness participation. 203 | */ 204 | recent_slots_filled: Bignum 205 | participation_count: number 206 | last_irreversible_block_num: number 207 | /** 208 | * The maximum bandwidth the blockchain can support is: 209 | * 210 | * max_bandwidth = maximum_block_size * STEEMIT_BANDWIDTH_AVERAGE_WINDOW_SECONDS / STEEMIT_BLOCK_INTERVAL 211 | * 212 | * The maximum virtual bandwidth is: 213 | * 214 | * max_bandwidth * current_reserve_ratio 215 | */ 216 | max_virtual_bandwidth: Bignum 217 | /** 218 | * Any time average_block_size <= 50% maximum_block_size this value grows by 1 until it 219 | * reaches STEEMIT_MAX_RESERVE_RATIO. Any time average_block_size is greater than 220 | * 50% it falls by 1%. Upward adjustments happen once per round, downward adjustments 221 | * happen every block. 222 | */ 223 | current_reserve_ratio: number 224 | /** 225 | * The number of votes regenerated per day. Any user voting slower than this rate will be 226 | * "wasting" voting power through spillover; any user voting faster than this rate will have 227 | * their votes reduced. 228 | */ 229 | vote_power_reserve_rate: number 230 | } 231 | 232 | /** 233 | * Return the vesting share price. 234 | */ 235 | export function getVestingSharePrice(props: DynamicGlobalProperties): Price { 236 | const totalVestingFund = Asset.from(props.total_vesting_fund_steem) 237 | const totalVestingShares = Asset.from(props.total_vesting_shares) 238 | if (totalVestingFund.amount === 0 || totalVestingShares.amount === 0) { 239 | return new Price(new Asset(1, 'VESTS'), new Asset(1, 'STEEM')) 240 | } 241 | return new Price(totalVestingShares, totalVestingFund) 242 | } 243 | 244 | /** 245 | * Returns the vests of specified account. Default: Subtract delegated & add received 246 | */ 247 | export function getVests(account: Account, subtract_delegated: boolean = true, add_received: boolean = true) { 248 | let vests: Asset = Asset.from(account.vesting_shares) 249 | const vests_delegated: Asset = Asset.from(account.delegated_vesting_shares) 250 | const vests_received: Asset = Asset.from(account.received_vesting_shares) 251 | const withdraw_rate: Asset = Asset.from(account.vesting_withdraw_rate) 252 | const already_withdrawn = (Number(account.to_withdraw) - Number(account.withdrawn)) / 1000000 253 | const withdraw_vests = Math.min(withdraw_rate.amount, already_withdrawn) 254 | vests = vests.subtract(withdraw_vests) 255 | 256 | if (subtract_delegated) { 257 | vests = vests.subtract(vests_delegated) 258 | } 259 | if (add_received) { 260 | vests = vests.add(vests_received) 261 | } 262 | 263 | return vests.amount 264 | } 265 | -------------------------------------------------------------------------------- /docs/interfaces/beneficiaryroute.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | BeneficiaryRoute | dsteem 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

Interface BeneficiaryRoute

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Hierarchy

71 |
    72 |
  • 73 | BeneficiaryRoute 74 |
  • 75 |
76 |
77 |
78 |

Index

79 |
80 |
81 |
82 |

Properties

83 | 87 |
88 |
89 |
90 |
91 |
92 |

Properties

93 |
94 | 95 |

account

96 |
account: string
97 | 102 |
103 |
104 | 105 |

weight

106 |
weight: number
107 | 112 |
113 |
114 |
115 | 143 |
144 |
145 |
146 |
147 |

Legend

148 |
149 |
    150 |
  • Module
  • 151 |
  • Object literal
  • 152 |
  • Variable
  • 153 |
  • Function
  • 154 |
  • Function with type parameter
  • 155 |
  • Index signature
  • 156 |
  • Type alias
  • 157 |
158 |
    159 |
  • Enumeration
  • 160 |
  • Enumeration member
  • 161 |
  • Property
  • 162 |
  • Method
  • 163 |
164 |
    165 |
  • Interface
  • 166 |
  • Interface with type parameter
  • 167 |
  • Constructor
  • 168 |
  • Property
  • 169 |
  • Method
  • 170 |
  • Index signature
  • 171 |
172 |
    173 |
  • Class
  • 174 |
  • Class with type parameter
  • 175 |
  • Constructor
  • 176 |
  • Property
  • 177 |
  • Method
  • 178 |
  • Accessor
  • 179 |
  • Index signature
  • 180 |
181 |
    182 |
  • Inherited constructor
  • 183 |
  • Inherited property
  • 184 |
  • Inherited method
  • 185 |
  • Inherited accessor
  • 186 |
187 |
    188 |
  • Protected property
  • 189 |
  • Protected method
  • 190 |
  • Protected accessor
  • 191 |
192 |
    193 |
  • Private property
  • 194 |
  • Private method
  • 195 |
  • Private accessor
  • 196 |
197 |
    198 |
  • Static property
  • 199 |
  • Static method
  • 200 |
201 |
202 |
203 |
204 |
205 |

Generated using TypeDoc

206 |
207 |
208 | 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /docs/enums/blockchainmode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | BlockchainMode | dsteem 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

Enumeration BlockchainMode

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Index

71 |
72 |
73 |
74 |

Enumeration members

75 | 79 |
80 |
81 |
82 |
83 |
84 |

Enumeration members

85 |
86 | 87 |

Irreversible

88 |
Irreversible:
89 | 94 |
95 |
96 |

Only get irreversible blocks.

97 |
98 |
99 |
100 |
101 | 102 |

Latest

103 |
Latest:
104 | 109 |
110 |
111 |

Get all blocks.

112 |
113 |
114 |
115 |
116 |
117 | 145 |
146 |
147 |
148 |
149 |

Legend

150 |
151 |
    152 |
  • Module
  • 153 |
  • Object literal
  • 154 |
  • Variable
  • 155 |
  • Function
  • 156 |
  • Function with type parameter
  • 157 |
  • Index signature
  • 158 |
  • Type alias
  • 159 |
160 |
    161 |
  • Enumeration
  • 162 |
  • Enumeration member
  • 163 |
  • Property
  • 164 |
  • Method
  • 165 |
166 |
    167 |
  • Interface
  • 168 |
  • Interface with type parameter
  • 169 |
  • Constructor
  • 170 |
  • Property
  • 171 |
  • Method
  • 172 |
  • Index signature
  • 173 |
174 |
    175 |
  • Class
  • 176 |
  • Class with type parameter
  • 177 |
  • Constructor
  • 178 |
  • Property
  • 179 |
  • Method
  • 180 |
  • Accessor
  • 181 |
  • Index signature
  • 182 |
183 |
    184 |
  • Inherited constructor
  • 185 |
  • Inherited property
  • 186 |
  • Inherited method
  • 187 |
  • Inherited accessor
  • 188 |
189 |
    190 |
  • Protected property
  • 191 |
  • Protected method
  • 192 |
  • Protected accessor
  • 193 |
194 |
    195 |
  • Private property
  • 196 |
  • Private method
  • 197 |
  • Private accessor
  • 198 |
199 |
    200 |
  • Static property
  • 201 |
  • Static method
  • 202 |
203 |
204 |
205 |
206 |
207 |

Generated using TypeDoc

208 |
209 |
210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /test/operations.ts: -------------------------------------------------------------------------------- 1 | import 'mocha' 2 | import * as assert from 'assert' 3 | import {randomBytes} from 'crypto' 4 | 5 | import * as ds from './../src' 6 | 7 | const {Asset, PrivateKey, Client, HexBuffer} = ds 8 | 9 | import {getTestnetAccounts, randomString, agent} from './common' 10 | 11 | describe('operations', function() { 12 | this.slow(20 * 1000) 13 | this.timeout(60 * 1000) 14 | 15 | const client = Client.testnet({agent}) 16 | 17 | let acc1: {username: string, password: string}, acc2: {username: string, password: string} 18 | let acc1Key: ds.PrivateKey 19 | before(async function() { 20 | [acc1, acc2] = await getTestnetAccounts() 21 | acc1Key = PrivateKey.fromLogin(acc1.username, acc1.password, 'active') 22 | }) 23 | 24 | it('should delegate vesting shares', async function() { 25 | const [user1] = await client.database.getAccounts([acc1.username]) 26 | const currentDelegation = Asset.from(user1.received_vesting_shares) 27 | const newDelegation = Asset.from( 28 | currentDelegation.amount >= 1000 ? 0 : 1000 + Math.random() * 1000, 29 | 'VESTS' 30 | ) 31 | const result = await client.broadcast.delegateVestingShares({ 32 | delegator: acc1.username, 33 | delegatee: acc2.username, 34 | vesting_shares: newDelegation 35 | }, acc1Key) 36 | const [user2] = await client.database.getAccounts([acc2.username]) 37 | assert.equal(user2.received_vesting_shares, newDelegation.toString()) 38 | }) 39 | 40 | it('should send custom', async function() { 41 | const props = await client.database.getDynamicGlobalProperties() 42 | const op: ds.CustomOperation = ['custom', { 43 | required_auths: [acc1.username], 44 | id: ~~(Math.random() * 65535), 45 | data: randomBytes(512), 46 | }] 47 | const rv = await client.broadcast.sendOperations([op], acc1Key) 48 | const tx = await client.database.getTransaction(rv) 49 | const rop = tx.operations[0] 50 | assert.equal(rop[0], 'custom') 51 | assert.equal(rop[1].data, HexBuffer.from(op[1].data).toString()) 52 | }) 53 | 54 | it('should send custom json', async function() { 55 | const data = {test: 123, string: 'unicode🐳'} 56 | const rv = await client.broadcast.json({ 57 | required_auths: [acc1.username], 58 | required_posting_auths: [], 59 | id: 'something', 60 | json: JSON.stringify(data), 61 | }, acc1Key) 62 | const tx = await client.database.getTransaction(rv) 63 | assert.deepEqual(JSON.parse(tx.operations[0][1].json), data) 64 | }) 65 | 66 | it('should transfer steem', async function() { 67 | const [acc2bf] = await client.database.getAccounts([acc2.username]) 68 | await client.broadcast.transfer({ 69 | from: acc1.username, 70 | to: acc2.username, 71 | amount: '0.001 TESTS', 72 | memo: 'Hej på dig!', 73 | }, acc1Key) 74 | const [acc2af] = await client.database.getAccounts([acc2.username]) 75 | const old_bal = Asset.from(acc2bf.balance); 76 | const new_bal = Asset.from(acc2af.balance); 77 | assert.equal(new_bal.subtract(old_bal).toString(), '0.001 TESTS') 78 | }) 79 | 80 | it('should create account and post with options', async function() { 81 | // ensure not testing accounts on mainnet 82 | assert(client.chainId.toString('hex') !== '0000000000000000000000000000000000000000000000000000000000000000') 83 | 84 | const username = 'ds-' + randomString(12) 85 | const password = randomString(32) 86 | await client.broadcast.createTestAccount({ 87 | username, password, creator: acc1.username, metadata: {date: new Date()} 88 | }, acc1Key) 89 | 90 | const [newAcc] = await client.database.getAccounts([username]) 91 | assert.equal(newAcc.name, username) 92 | // not sure why but on the testnet the recovery account is always 'steem' 93 | // assert.equal(newAcc.recovery_account, acc1.username) 94 | const postingWif = PrivateKey.fromLogin(username, password, 'posting') 95 | const postingPub = postingWif.createPublic(client.addressPrefix).toString() 96 | const memoWif = PrivateKey.fromLogin(username, password, 'memo') 97 | const memoPub = memoWif.createPublic(client.addressPrefix).toString() 98 | assert.equal(newAcc.memo_key, memoPub) 99 | assert.equal(newAcc.posting.key_auths[0][0], postingPub) 100 | 101 | const permlink = 'hello-world' 102 | await client.broadcast.commentWithOptions({ 103 | parent_author: '', 104 | parent_permlink: 'test', 105 | author: username, 106 | permlink, 107 | title: 'Hello world!', 108 | body: `My password is: ${ password }`, 109 | json_metadata: JSON.stringify({tags: ['test', 'hello']}), 110 | }, { 111 | permlink, author: username, 112 | allow_votes: false, 113 | allow_curation_rewards: false, 114 | percent_steem_dollars: 0, 115 | max_accepted_payout: Asset.from(10, 'TBD'), 116 | extensions: [ 117 | [0, {beneficiaries: [ 118 | {weight: 10000, account: acc1.username} 119 | ]}] 120 | ], 121 | }, postingWif) 122 | 123 | const [post] = await client.call('condenser_api', 'get_content', [username, permlink]) 124 | assert.deepEqual(post.beneficiaries, [{account: acc1.username, weight: 10000}]) 125 | assert.equal(post.max_accepted_payout, '10.000 TBD') 126 | assert.equal(post.percent_steem_dollars, 0) 127 | assert.equal(post.allow_votes, false) 128 | }) 129 | 130 | it('should update account', async function() { 131 | const key = PrivateKey.fromLogin(acc1.username, acc1.password, 'active') 132 | const foo = Math.random() 133 | const rv = await client.broadcast.updateAccount({ 134 | account: acc1.username, 135 | memo_key: PrivateKey.fromLogin(acc1.username, acc1.password, 'memo').createPublic(client.addressPrefix), 136 | json_metadata: JSON.stringify({foo}), 137 | }, key) 138 | const [acc] = await client.database.getAccounts([acc1.username]) 139 | assert.deepEqual({foo}, JSON.parse(acc.json_metadata)) 140 | }) 141 | 142 | it('should create account custom auths', async function() { 143 | const key = PrivateKey.fromLogin(acc1.username, acc1.password, 'active') 144 | 145 | const username = 'ds-' + randomString(12) 146 | const password = randomString(32) 147 | const metadata = {my_password_is: password} 148 | 149 | const ownerKey = PrivateKey.fromLogin(username, password, 'owner').createPublic(client.addressPrefix) 150 | const activeKey = PrivateKey.fromLogin(username, password, 'active').createPublic(client.addressPrefix) 151 | const postingKey = PrivateKey.fromLogin(username, password, 'posting').createPublic(client.addressPrefix) 152 | const memoKey = PrivateKey.fromLogin(username, password, 'memo').createPublic(client.addressPrefix) 153 | await client.broadcast.createTestAccount({ 154 | creator: acc1.username, 155 | username, 156 | auths: { 157 | owner: ownerKey, 158 | active: activeKey.toString(), 159 | posting: {weight_threshold: 1, account_auths: [], key_auths: [[postingKey, 1]]}, 160 | memoKey, 161 | }, 162 | metadata 163 | }, key) 164 | const [newAccount] = await client.database.getAccounts([username]) 165 | assert.equal(newAccount.name, username) 166 | assert.equal(newAccount.memo_key, memoKey) 167 | }) 168 | 169 | it('should create account and calculate fees', async function() { 170 | const password = randomString(32) 171 | const metadata = {my_password_is: password} 172 | const creator = acc1.username 173 | 174 | // ensure not testing accounts on mainnet 175 | assert(client.chainId.toString('hex') !== '0000000000000000000000000000000000000000000000000000000000000000') 176 | 177 | const chainProps = await client.database.getChainProperties() 178 | const creationFee = Asset.from(chainProps.account_creation_fee) 179 | 180 | // no delegation and no fee (uses RC instead) 181 | await client.broadcast.createTestAccount({ 182 | password, metadata, creator, username: 'foo' + randomString(12), 183 | delegation: 0 184 | }, acc1Key) 185 | 186 | // fee (no RC used) and no delegation 187 | await client.broadcast.createTestAccount({ 188 | password, metadata, creator, username: 'foo' + randomString(12), 189 | fee: creationFee 190 | }, acc1Key) 191 | 192 | // fee plus delegation 193 | await client.broadcast.createTestAccount({ 194 | password, creator, username: 'foo' + randomString(12), 195 | fee: creationFee, delegation: Asset.from(1000, 'VESTS') 196 | }, acc1Key) 197 | 198 | // invalid (inexact) fee must fail 199 | try { 200 | await client.broadcast.createTestAccount({password, metadata, creator, username: 'foo', fee: '1.111 TESTS'}, acc1Key) 201 | assert(false, 'should not be reached') 202 | } catch (error) { 203 | assert.equal(error.message, 'Fee must be exactly ' + creationFee.toString()) 204 | } 205 | 206 | try { 207 | await client.broadcast.createTestAccount({metadata, creator, username: 'foo'}, acc1Key) 208 | assert(false, 'should not be reached') 209 | } catch (error) { 210 | assert.equal(error.message, 'Must specify either password or auths') 211 | } 212 | }) 213 | 214 | it('should change recovery account', async function() { 215 | const op: ds.ChangeRecoveryAccountOperation = ['change_recovery_account', { 216 | account_to_recover: acc1.username, 217 | new_recovery_account: acc2.username, 218 | extensions: [], 219 | }] 220 | const key = PrivateKey.fromLogin(acc1.username, acc1.password, 'active') 221 | await client.broadcast.sendOperations([op], key) 222 | }) 223 | 224 | it('should report overproduction', async function() { 225 | const b1 = await client.database.getBlock(10) 226 | const b2 = await client.database.getBlock(11) 227 | b1.timestamp = b2.timestamp 228 | const op: ds.ReportOverProductionOperation = ['report_over_production', { 229 | reporter: acc1.username, 230 | first_block: b1, 231 | second_block: b2, 232 | }] 233 | try { 234 | await client.broadcast.sendOperations([op], acc1Key) 235 | assert(false) 236 | } catch (error) { 237 | assert.equal(error.message, 'first_block.signee() == second_block.signee(): ') 238 | } 239 | }) 240 | 241 | }) 242 | -------------------------------------------------------------------------------- /docs/interfaces/resource.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Resource | dsteem 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 27 |
28 |
29 | Options 30 |
31 |
32 | All 33 |
    34 |
  • Public
  • 35 |
  • Public/Protected
  • 36 |
  • All
  • 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | Menu 48 |
49 |
50 |
51 |
52 |
53 |
54 | 62 |

Interface Resource

63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |

Hierarchy

71 |
    72 |
  • 73 | Resource 74 |
  • 75 |
76 |
77 |
78 |

Index

79 |
80 |
81 |
82 |

Properties

83 | 87 |
88 |
89 |
90 |
91 |
92 |

Properties

93 |
94 | 95 |

price_curve_params

96 |
price_curve_params: PriceCurveParam
97 | 102 |
103 |
104 | 105 |

resource_dynamics_params

106 |
resource_dynamics_params: DynamicParam
107 | 112 |
113 |
114 |
115 | 143 |
144 |
145 |
146 |
147 |

Legend

148 |
149 |
    150 |
  • Module
  • 151 |
  • Object literal
  • 152 |
  • Variable
  • 153 |
  • Function
  • 154 |
  • Function with type parameter
  • 155 |
  • Index signature
  • 156 |
  • Type alias
  • 157 |
158 |
    159 |
  • Enumeration
  • 160 |
  • Enumeration member
  • 161 |
  • Property
  • 162 |
  • Method
  • 163 |
164 |
    165 |
  • Interface
  • 166 |
  • Interface with type parameter
  • 167 |
  • Constructor
  • 168 |
  • Property
  • 169 |
  • Method
  • 170 |
  • Index signature
  • 171 |
172 |
    173 |
  • Class
  • 174 |
  • Class with type parameter
  • 175 |
  • Constructor
  • 176 |
  • Property
  • 177 |
  • Method
  • 178 |
  • Accessor
  • 179 |
  • Index signature
  • 180 |
181 |
    182 |
  • Inherited constructor
  • 183 |
  • Inherited property
  • 184 |
  • Inherited method
  • 185 |
  • Inherited accessor
  • 186 |
187 |
    188 |
  • Protected property
  • 189 |
  • Protected method
  • 190 |
  • Protected accessor
  • 191 |
192 |
    193 |
  • Private property
  • 194 |
  • Private method
  • 195 |
  • Private accessor
  • 196 |
197 |
    198 |
  • Static property
  • 199 |
  • Static method
  • 200 |
201 |
202 |
203 |
204 |
205 |

Generated using TypeDoc

206 |
207 |
208 | 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Steem RPC client implementation. 3 | * @author Johan Nordberg 4 | * @license 5 | * Copyright (c) 2017 Johan Nordberg. All Rights Reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without modification, 8 | * are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistribution of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 13 | * 2. Redistribution in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * 3. Neither the name of the copyright holder nor the names of its contributors 18 | * may be used to endorse or promote products derived from this software without 19 | * specific prior written permission. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 29 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 | * OF THE POSSIBILITY OF SUCH DAMAGE. 31 | * 32 | * You acknowledge that this software is not designed, licensed or intended for use 33 | * in the design, construction, operation or maintenance of any military facility. 34 | */ 35 | 36 | import * as assert from 'assert' 37 | import {VError} from 'verror' 38 | import packageVersion from './version' 39 | 40 | import {Blockchain} from './helpers/blockchain' 41 | import {BroadcastAPI} from './helpers/broadcast' 42 | import {DatabaseAPI} from './helpers/database' 43 | import {RCAPI} from './helpers/rc' 44 | import {copy, retryingFetch, waitForEvent} from './utils' 45 | 46 | /** 47 | * Library version. 48 | */ 49 | export const VERSION = packageVersion 50 | 51 | /** 52 | * Main steem network chain id. 53 | */ 54 | export const DEFAULT_CHAIN_ID = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex') 55 | 56 | /** 57 | * Main steem network address prefix. 58 | */ 59 | export const DEFAULT_ADDRESS_PREFIX = 'STM' 60 | 61 | interface RPCRequest { 62 | /** 63 | * Request sequence number. 64 | */ 65 | id: number | string 66 | /** 67 | * RPC method. 68 | */ 69 | method: 'call' | 'notice' | 'callback' 70 | /** 71 | * Array of parameters to pass to the method. 72 | */ 73 | jsonrpc: '2.0' 74 | params: any[] 75 | } 76 | 77 | interface RPCCall extends RPCRequest { 78 | method: 'call' 79 | /** 80 | * 1. API to call, you can pass either the numerical id of the API you get 81 | * from calling 'get_api_by_name' or the name directly as a string. 82 | * 2. Method to call on that API. 83 | * 3. Arguments to pass to the method. 84 | */ 85 | params: [number|string, string, any[]] 86 | } 87 | 88 | interface RPCError { 89 | code: number 90 | message: string 91 | data?: any 92 | } 93 | 94 | interface RPCResponse { 95 | /** 96 | * Response sequence number, corresponding to request sequence number. 97 | */ 98 | id: number 99 | error?: RPCError 100 | result?: any 101 | } 102 | 103 | interface PendingRequest { 104 | request: RPCRequest, 105 | timer: NodeJS.Timer | undefined 106 | resolve: (response: any) => void 107 | reject: (error: Error) => void 108 | } 109 | 110 | /** 111 | * RPC Client options 112 | * ------------------ 113 | */ 114 | export interface ClientOptions { 115 | /** 116 | * Steem chain id. Defaults to main steem network: 117 | * `0000000000000000000000000000000000000000000000000000000000000000` 118 | */ 119 | chainId?: string 120 | /** 121 | * Steem address prefix. Defaults to main steem network: 122 | * `STM` 123 | */ 124 | addressPrefix?: string 125 | /** 126 | * Send timeout, how long to wait in milliseconds before giving 127 | * up on a rpc call. Note that this is not an exact timeout, 128 | * no in-flight requests will be aborted, they will just not 129 | * be retried any more past the timeout. 130 | * Can be set to 0 to retry forever. Defaults to 60 * 1000 ms. 131 | */ 132 | timeout?: number 133 | /** 134 | * Retry backoff function, returns milliseconds. Default = {@link defaultBackoff}. 135 | */ 136 | backoff?: (tries: number) => number 137 | /** 138 | * Node.js http(s) agent, use if you want http keep-alive. 139 | * Defaults to using https.globalAgent. 140 | * @see https://nodejs.org/api/http.html#http_new_agent_options. 141 | */ 142 | agent?: any // https.Agent 143 | } 144 | 145 | /** 146 | * RPC Client 147 | * ---------- 148 | * Can be used in both node.js and the browser. Also see {@link ClientOptions}. 149 | */ 150 | export class Client { 151 | 152 | /** 153 | * Create a new client instance configured for the testnet. 154 | */ 155 | public static testnet(options?: ClientOptions) { 156 | let opts: ClientOptions = {} 157 | if (options) { 158 | opts = copy(options) 159 | opts.agent = options.agent 160 | } 161 | 162 | opts.addressPrefix = 'STX' 163 | opts.chainId = '79276aea5d4877d9a25892eaa01b0adf019d3e5cb12a97478df3298ccdd01673' 164 | return new Client('https://testnet.steem.vc', opts) 165 | } 166 | 167 | /** 168 | * Client options, *read-only*. 169 | */ 170 | public readonly options: ClientOptions 171 | 172 | /** 173 | * Address to Steem RPC server, *read-only*. 174 | */ 175 | public readonly address: string 176 | 177 | /** 178 | * Database API helper. 179 | */ 180 | public readonly database: DatabaseAPI 181 | 182 | /** 183 | * RC API helper. 184 | */ 185 | public readonly rc: RCAPI 186 | 187 | /** 188 | * Broadcast API helper. 189 | */ 190 | public readonly broadcast: BroadcastAPI 191 | 192 | /** 193 | * Blockchain helper. 194 | */ 195 | public readonly blockchain: Blockchain 196 | 197 | /** 198 | * Chain ID for current network. 199 | */ 200 | public readonly chainId: Buffer 201 | 202 | /** 203 | * Address prefix for current network. 204 | */ 205 | public readonly addressPrefix: string 206 | 207 | private timeout: number 208 | private backoff: typeof defaultBackoff 209 | 210 | /** 211 | * @param address The address to the Steem RPC server, e.g. `https://api.steemit.com`. 212 | * @param options Client options. 213 | */ 214 | constructor(address: string, options: ClientOptions = {}) { 215 | this.address = address 216 | this.options = options 217 | 218 | this.chainId = options.chainId ? Buffer.from(options.chainId, 'hex') : DEFAULT_CHAIN_ID 219 | assert.equal(this.chainId.length, 32, 'invalid chain id') 220 | this.addressPrefix = options.addressPrefix || DEFAULT_ADDRESS_PREFIX 221 | 222 | this.timeout = options.timeout || 60 * 1000 223 | this.backoff = options.backoff || defaultBackoff 224 | 225 | this.database = new DatabaseAPI(this) 226 | this.broadcast = new BroadcastAPI(this) 227 | this.blockchain = new Blockchain(this) 228 | this.rc = new RCAPI(this) 229 | } 230 | 231 | /** 232 | * Make a RPC call to the server. 233 | * 234 | * @param api The API to call, e.g. `database_api`. 235 | * @param method The API method, e.g. `get_dynamic_global_properties`. 236 | * @param params Array of parameters to pass to the method, optional. 237 | * 238 | */ 239 | public async call(api: string, method: string, params: any = []): Promise { 240 | const request: RPCCall = { 241 | id: '0', 242 | jsonrpc: '2.0', 243 | method: 'call', 244 | params: [api, method, params], 245 | } 246 | const body = JSON.stringify(request, (key, value) => { 247 | // encode Buffers as hex strings instead of an array of bytes 248 | if (typeof value === 'object' && value.type === 'Buffer') { 249 | return Buffer.from(value.data).toString('hex') 250 | } 251 | return value 252 | }) 253 | const opts: any = { 254 | body, 255 | cache: 'no-cache', 256 | headers: {'User-Agent': `dsteem/${ packageVersion }`}, 257 | method: 'POST', 258 | mode: 'cors', 259 | } 260 | if (this.options.agent) { 261 | opts.agent = this.options.agent 262 | } 263 | let fetchTimeout: any 264 | if (api !== 'network_broadcast_api' && method.substring(0, 21) !== 'broadcast_transaction') { 265 | // bit of a hack to work around some nodes high error rates 266 | // only effective in node.js (until timeout spec lands in browsers) 267 | fetchTimeout = (tries) => (tries + 1) * 500 268 | } 269 | const response: RPCResponse = await retryingFetch( 270 | this.address, opts, this.timeout, this.backoff, fetchTimeout 271 | ) 272 | // resolve FC error messages into something more readable 273 | if (response.error) { 274 | const formatValue = (value: any) => { 275 | switch (typeof value) { 276 | case 'object': 277 | return JSON.stringify(value) 278 | default: 279 | return String(value) 280 | } 281 | } 282 | const {data} = response.error 283 | let {message} = response.error 284 | if (data && data.stack && data.stack.length > 0) { 285 | const top = data.stack[0] 286 | const topData = copy(top.data) 287 | message = top.format.replace(/\$\{([a-z_]+)\}/gi, (match: string, key: string) => { 288 | let rv = match 289 | if (topData[key]) { 290 | rv = formatValue(topData[key]) 291 | delete topData[key] 292 | } 293 | return rv 294 | }) 295 | const unformattedData = Object.keys(topData) 296 | .map((key) => ({key, value: formatValue(topData[key])})) 297 | .map((item) => `${ item.key }=${ item.value}`) 298 | if (unformattedData.length > 0) { 299 | message += ' ' + unformattedData.join(' ') 300 | } 301 | } 302 | throw new VError({info: data, name: 'RPCError'}, message) 303 | } 304 | assert.equal(response.id, request.id, 'got invalid response id') 305 | return response.result 306 | } 307 | 308 | } 309 | 310 | /** 311 | * Default backoff function. 312 | * ```min(tries*10^2, 10 seconds)``` 313 | */ 314 | const defaultBackoff = (tries: number): number => { 315 | return Math.min(Math.pow(tries * 10, 2), 10 * 1000) 316 | } 317 | --------------------------------------------------------------------------------