@esteemapp/dhive
Version:
Hive blockchain RPC client library
280 lines (279 loc) • 12 kB
JavaScript
"use strict";
/**
* @file Broadcast API helpers.
* @author Johan Nordberg <code@johan-nordberg.com>
* @license
* Copyright (c) 2017 Johan Nordberg. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistribution of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You acknowledge that this software is not designed, licensed or intended for use
* in the design, construction, operation or maintenance of any military facility.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const assert = require("assert");
const account_1 = require("../chain/account");
const asset_1 = require("../chain/asset");
const crypto_1 = require("./../crypto");
const HF23_CHAIN_ID = '0000000000000000000000000000000000000000000000000000000000000000';
const HF24_CHAIN_ID = 'beeab0de00000000000000000000000000000000000000000000000000000000';
class BroadcastAPI {
constructor(client) {
this.client = client;
/**
* How many milliseconds in the future to set the expiry time to when
* broadcasting a transaction, defaults to 1 minute.
*/
this.expireTime = 60 * 1000;
}
/**
* Broadcast a comment, also used to create a new top level post.
* @param comment The comment/post.
* @param key Private posting key of comment author.
*/
comment(comment, key) {
return __awaiter(this, void 0, void 0, function* () {
const op = ['comment', comment];
return this.sendOperations([op], key);
});
}
/**
* Broadcast a comment and set the options.
* @param comment The comment/post.
* @param options The comment/post options.
* @param key Private posting key of comment author.
*/
commentWithOptions(comment, options, key) {
return __awaiter(this, void 0, void 0, function* () {
const ops = [
['comment', comment],
['comment_options', options]
];
return this.sendOperations(ops, key);
});
}
/**
* Broadcast a vote.
* @param vote The vote to send.
* @param key Private posting key of the voter.
*/
vote(vote, key) {
return __awaiter(this, void 0, void 0, function* () {
const op = ['vote', vote];
return this.sendOperations([op], key);
});
}
/**
* Broadcast a transfer.
* @param data The transfer operation payload.
* @param key Private active key of sender.
*/
transfer(data, key) {
return __awaiter(this, void 0, void 0, function* () {
const op = ['transfer', data];
return this.sendOperations([op], key);
});
}
/**
* Broadcast custom JSON.
* @param data The custom_json operation payload.
* @param key Private posting or active key.
*/
json(data, key) {
return __awaiter(this, void 0, void 0, function* () {
const op = ['custom_json', data];
return this.sendOperations([op], key);
});
}
/**
* Create a new account on testnet.
* @param options New account options.
* @param key Private active key of account creator.
*/
createTestAccount(options, key) {
return __awaiter(this, void 0, void 0, function* () {
assert(global.hasOwnProperty('it'), 'helper to be used only for mocha tests');
const { username, metadata, creator } = options;
const prefix = this.client.addressPrefix;
let owner, active, posting, memo_key;
if (options.password) {
const ownerKey = crypto_1.PrivateKey.fromLogin(username, options.password, 'owner').createPublic(prefix);
owner = account_1.Authority.from(ownerKey);
const activeKey = crypto_1.PrivateKey.fromLogin(username, options.password, 'active').createPublic(prefix);
active = account_1.Authority.from(activeKey);
const postingKey = crypto_1.PrivateKey.fromLogin(username, options.password, 'posting').createPublic(prefix);
posting = account_1.Authority.from(postingKey);
memo_key = crypto_1.PrivateKey.fromLogin(username, options.password, 'memo').createPublic(prefix);
}
else if (options.auths) {
owner = account_1.Authority.from(options.auths.owner);
active = account_1.Authority.from(options.auths.active);
posting = account_1.Authority.from(options.auths.posting);
memo_key = crypto_1.PublicKey.from(options.auths.memoKey);
}
else {
throw new Error('Must specify either password or auths');
}
let { fee, delegation } = options;
delegation = asset_1.Asset.from(delegation || 0, 'VESTS');
fee = asset_1.Asset.from(fee || 0, 'TESTS');
if (fee.amount > 0) {
const chainProps = yield this.client.database.getChainProperties();
const creationFee = asset_1.Asset.from(chainProps.account_creation_fee);
if (fee.amount !== creationFee.amount) {
throw new Error('Fee must be exactly ' + creationFee.toString());
}
}
const claim_op = [
'claim_account',
{
creator,
extensions: [],
fee
}
];
const create_op = [
'create_claimed_account',
{
active,
creator,
extensions: [],
json_metadata: metadata ? JSON.stringify(metadata) : '',
memo_key,
new_account_name: username,
owner,
posting
}
];
const ops = [claim_op, create_op];
if (delegation.amount > 0) {
const delegate_op = [
'delegate_vesting_shares',
{
delegatee: username,
delegator: creator,
vesting_shares: delegation
}
];
ops.push(delegate_op);
}
return this.sendOperations(ops, key);
});
}
/**
* Update account.
* @param data The account_update payload.
* @param key The private key of the account affected, should be the corresponding
* key level or higher for updating account authorities.
*/
updateAccount(data, key) {
return __awaiter(this, void 0, void 0, function* () {
const op = ['account_update', data];
return this.sendOperations([op], key);
});
}
/**
* Delegate vesting shares from one account to the other. The vesting shares are still owned
* by the original account, but content voting rights and bandwidth allocation are transferred
* to the receiving account. This sets the delegation to `vesting_shares`, increasing it or
* decreasing it as needed. (i.e. a delegation of 0 removes the delegation)
*
* When a delegation is removed the shares are placed in limbo for a week to prevent a satoshi
* of VESTS from voting on the same content twice.
*
* @param options Delegation options.
* @param key Private active key of the delegator.
*/
delegateVestingShares(options, key) {
return __awaiter(this, void 0, void 0, function* () {
const op = ['delegate_vesting_shares', options];
return this.sendOperations([op], key);
});
}
/**
* Sign and broadcast transaction with operations to the network. Throws if the transaction expires.
* @param operations List of operations to send.
* @param key Private key(s) used to sign transaction.
*/
sendOperations(operations, key) {
return __awaiter(this, void 0, void 0, function* () {
const props = yield this.client.database.getDynamicGlobalProperties();
const HFV = yield this.client.database.call('get_hardfork_version');
if (HFV === '0.23.0') {
this.client.chainId = Buffer.from(HF23_CHAIN_ID, 'hex');
}
else {
this.client.chainId = Buffer.from(HF24_CHAIN_ID, 'hex');
}
const ref_block_num = props.head_block_number & 0xffff;
const ref_block_prefix = Buffer.from(props.head_block_id, 'hex').readUInt32LE(4);
const expiration = new Date(new Date(props.time + 'Z').getTime() + this.expireTime)
.toISOString()
.slice(0, -5);
const extensions = [];
const tx = {
expiration,
extensions,
operations,
ref_block_num,
ref_block_prefix
};
const result = yield this.send(this.sign(tx, key));
assert(result.expired === false, 'transaction expired');
return result;
});
}
/**
* Sign a transaction with key(s).
*/
sign(transaction, key) {
return crypto_1.cryptoUtils.signTransaction(transaction, key, this.client.chainId);
}
/**
* Broadcast a signed transaction to the network.
*/
send(transaction) {
return __awaiter(this, void 0, void 0, function* () {
return this.call('broadcast_transaction_synchronous', [transaction]);
});
}
/**
* Convenience for calling `condenser_api`.
*/
call(method, params) {
return this.client.call('condenser_api', method, params);
}
}
exports.BroadcastAPI = BroadcastAPI;