├── 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
6 | Hello there sailor, looks like your JavaScript is off. No es bueno! 7 |
8 |Loading...
12 |
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 | ` * 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 |
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) [](https://circleci.com/gh/jnordberg/workflows/dsteem) [](https://coveralls.io/github/jnordberg/dsteem?branch=master) [](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 | [](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