swtc-lib
Version:
websocket access for jingtum blockchain
824 lines • 30.7 kB
JavaScript
import { Account } from "./account";
import { EventEmitter } from "events";
import { OrderBook } from "./orderbook";
import { Request } from "./request";
import { Server } from "./server";
import { Transaction } from "@swtc/transaction";
import LRU from "lru-cache";
import sha1 from "sha1";
const Wallet = Transaction.Wallet;
const utils = Transaction.utils;
function getRelationType(type) {
switch (type) {
case "trust":
return 0;
case "authorize":
return 1;
case "freeze":
return 3;
default:
return null;
}
}
class Remote extends EventEmitter {
constructor(options = { local_sign: true }) {
super();
this.AbiCoder = null;
this.Tum3 = null;
this._url = `ws://${Remote.XLIB.default_ws ||
"ws.swtclib.ca:5020"}`;
this._url_failover = `ws://${Remote.XLIB.default_ws_failover ||
"ws-failover.swtclib.ca:5020"}`;
this._solidity = false;
this._timeout = 20 * 1000;
this._failover = false;
this.AlethEvent = function (options) {
const request = new Request(this, "aleth_eventlog", data => data);
if (typeof options !== "object") {
request.message.obj = new Error("invalid options type");
return request;
}
const des = options.destination;
const abi = options.abi;
if (!utils.isValidAddress(des)) {
request.message.des = new Error("invalid destination");
return request;
}
if (!abi) {
request.message.abi = new Error("not found abi");
return request;
}
if (!Array.isArray(abi)) {
request.message.params = new Error("invalid abi: type error.");
return request;
}
this.abi = abi;
request.message.Destination = des;
return request;
};
const _opts = options || {};
if (_opts.hasOwnProperty("timeout")) {
const timeout = Number(_opts.timeout);
this._timeout = timeout > 5 * 1000 ? timeout : 20 * 1000;
}
this._local_sign = true;
if (_opts.solidity) {
this._solidity = true;
try {
this.AbiCoder = require("tum3-eth-abi").AbiCoder;
this.Tum3 = require("swtc-tum3");
}
catch (error) {
throw Error("install tum3-eth-abi and swtc-tum3 to enable solidity support");
}
}
if (!_opts.hasOwnProperty("server")) {
this._failover = true;
}
else {
if (typeof _opts.server !== "string") {
this.type = new TypeError("server config not supplied");
return this;
}
this._url = _opts.server;
}
if (_opts.hasOwnProperty("server_failover")) {
if (typeof _opts.server_failover !== "string") {
this.type = new TypeError("server_failover config not supplied");
return this;
}
this._url_failover = _opts.server_failover;
}
if (_opts.failover) {
this._failover = true;
}
this._server = new Server(this, this._url);
this._status = {
ledger_index: 0
};
this._requests = {};
this._token = options.token || Wallet.token || "swt";
this._issuer =
options.issuer ||
Wallet.config.issuer ||
"jGa9J9TkqtBcUoHe2zqhVFFbgUVED6o9or";
this._cache = new LRU({
max: 100,
maxAge: 1000 * 60 * 5
});
this._paths = new LRU({
max: 100,
maxAge: 1000 * 60 * 5
});
this.on("newListener", type => {
if (!this._server.isConnected())
return;
if (type === "removeListener")
return;
if (type === "transactions") {
this.subscribe("transactions").submit();
}
if (type === "ledger_closed") {
this.subscribe("ledger").submit();
}
});
this.on("removeListener", type => {
if (!this._server.isConnected())
return;
if (type === "transactions") {
this.unsubscribe("transactions").submit();
}
if (type === "ledger_closed") {
this.unsubscribe("ledger").submit();
}
});
}
config() {
return {
_local_sign: this._local_sign,
_failover: this._failover,
_url: this._url,
_url_failover: this._url_failover,
_url_active: this._server._url,
_token: this._token,
_issuer: this._issuer,
_solidity: this._solidity,
_timeout: this._timeout
};
}
makeCurrency(currency = this._token, issuer = this._issuer) {
return Wallet.makeCurrency(currency, issuer);
}
makeAmount(value = 1, currency = this._token, issuer = this._issuer) {
return Wallet.makeAmount(value, currency, issuer);
}
connect(callback) {
if (!this._server)
return callback("server not ready");
this._server.connect(callback);
}
connectPromise() {
return new Promise((resolve, reject) => {
if (!this._server)
return reject(new Error("server not ready"));
this._server
.connectPromise()
.then(result => resolve(result))
.catch(error => {
if (!this._failover) {
reject(error);
}
else {
if (this._server._url === this._url) {
this._server = new Server(this, this._url_failover);
}
else {
this._server = new Server(this, this._url);
}
this._server
.connectPromise()
.then(result_failover => resolve(result_failover))
.catch(error_failover => reject(error_failover));
}
});
});
}
disconnect() {
if (!this._server)
return;
this._server.disconnect();
}
isConnected() {
return this._server.isConnected();
}
_handleMessage(e) {
let data;
let try_again = false;
try {
data = JSON.parse(e.data);
if (typeof data !== "object")
return;
switch (data.type) {
case "ledgerClosed":
this._handleLedgerClosed(data);
break;
case "serverStatus":
this._handleServerStatus(data);
break;
case "response":
this._handleResponse(data);
break;
case "transaction":
this._handleTransaction(data);
break;
case "path_find":
this._handlePathFind(data);
break;
}
}
catch (error) {
try_again = true;
}
if (try_again) {
try {
data = JSON.parse(e);
if (typeof data !== "object")
return;
switch (data.type) {
case "ledgerClosed":
this._handleLedgerClosed(data);
break;
case "serverStatus":
this._handleServerStatus(data);
break;
case "response":
this._handleResponse(data);
break;
case "transaction":
this._handleTransaction(data);
break;
case "path_find":
this._handlePathFind(data);
break;
}
}
catch (error) {
try_again = false;
}
}
}
_submit(command, data, filter, callback) {
const req_id = this._server.sendMessage(command, data);
this._requests[req_id] = {
command,
data,
filter,
callback
};
}
requestServerInfo() {
return new Request(this, "server_info", data => {
return {
complete_ledgers: data.info.complete_ledgers,
ledger: data.info.validated_ledger.hash,
public_key: data.info.pubkey_node,
state: data.info.server_state,
peers: data.info.peers,
version: "skywelld-" + data.info.build_version
};
});
}
requestPeers() {
return new Request(this, "peers", data => {
return data;
});
}
requestLedgerClosed() {
return new Request(this, "ledger_closed", data => {
return {
ledger_hash: data.ledger_hash,
ledger_index: data.ledger_index
};
});
}
requestLedger(options) {
const cmd = "ledger";
let filter = true;
const request = new Request(this, cmd, data => {
const ledger = data.ledger || data.closed.ledger;
if (!filter) {
return ledger;
}
return {
accepted: ledger.accepted,
ledger_hash: ledger.hash,
ledger_index: ledger.ledger_index,
parent_hash: ledger.parent_hash,
close_time: ledger.close_time_human,
total_coins: ledger.total_coins
};
});
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
if (options.ledger_index &&
!/^[1-9]\d{0,9}$/.test(String(options.ledger_index))) {
request.message.ledger_index = new Error("invalid ledger_index");
return request;
}
if (options.ledger_index) {
request.message.ledger_index = Number(options.ledger_index);
}
if (utils.isValidHash(options.ledger_hash)) {
request.message.ledger_hash = options.ledger_hash;
}
if ("full" in options && typeof options.full === "boolean") {
request.message.full = options.full;
filter = false;
}
if ("expand" in options && typeof options.expand === "boolean") {
request.message.expand = options.expand;
filter = false;
}
if ("transactions" in options &&
typeof options.transactions === "boolean") {
request.message.transactions = options.transactions;
filter = false;
}
if ("accounts" in options && typeof options.accounts === "boolean") {
request.message.accounts = options.accounts;
filter = false;
}
return request;
}
requestAccounts(options) {
const request = new Request(this, "account_count");
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
if (options.ledger_index &&
!/^[1-9]\d{0,9}$/.test(String(options.ledger_index))) {
request.message.ledger_index = new Error("invalid ledger_index");
return request;
}
if (options.ledger_index) {
request.message.ledger_index = Number(options.ledger_index);
}
if (utils.isValidHash(options.ledger_hash)) {
request.message.ledger_hash = options.ledger_hash;
}
if (options.marker) {
request.message.marker = options.marker;
}
return request;
}
requestTx(options) {
const request = new Request(this, "tx");
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
const hash = options.hash;
if (!utils.isValidHash(hash)) {
request.message.hash = new Error("invalid tx hash");
return request;
}
request.message.transaction = hash;
return request;
}
requestAccountInfo(options) {
const request = new Request(this);
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
return this.__requestAccount("account_info", options, request);
}
requestAccountTums(options) {
const request = new Request(this);
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
return this.__requestAccount("account_currencies", options, request);
}
requestAccountRelations(options) {
const request = new Request(this);
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
if (!~Transaction.RelationTypes.indexOf(options.type)) {
request.message.relation_type = new Error("invalid realtion type");
return request;
}
switch (options.type) {
case "trust":
return this.__requestAccount("account_lines", options, request);
case "authorize":
case "freeze":
return this.__requestAccount("account_relation", options, request);
}
request.message.msg = new Error("relation should not go here");
return request;
}
requestAccountOffers(options) {
const request = new Request(this);
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
return this.__requestAccount("account_offers", options, request);
}
requestAccountTx(options) {
const request = new Request(this, "account_tx", data => {
const results = [];
for (const data_transaction of data.transactions) {
results.push(utils.processTx(data_transaction, options.account));
}
data.transactions = results;
return data;
});
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
if (!utils.isValidAddress(options.account)) {
request.message.account = new Error("account parameter is invalid");
return request;
}
request.message.account = options.account;
if (options.ledger_min && Number(options.ledger_min)) {
request.message.ledger_index_min = Number(options.ledger_min);
}
else {
request.message.ledger_index_min = 0;
}
if (options.ledger_max && Number(options.ledger_max)) {
request.message.ledger_index_max = Number(options.ledger_max);
}
else {
request.message.ledger_index_max = -1;
}
if (options.limit && Number(options.limit)) {
request.message.limit = Number(options.limit);
}
if (options.offset && Number(options.offset)) {
request.message.offset = Number(options.offset);
}
if (typeof options.marker === "object" &&
!Number.isNaN(Number(options.marker.ledger)) &&
!Number.isNaN(Number(options.marker.seq))) {
request.message.marker = options.marker;
}
if (options.forward && typeof options.forward === "boolean") {
request.message.forward = options.forward;
}
return request;
}
requestOrderBook(options) {
const request = new Request(this, "book_offers");
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
const taker_gets = options.taker_gets || options.pays;
if (!utils.isValidAmount0(taker_gets)) {
request.message.taker_gets = new Error("invalid taker gets amount");
return request;
}
const taker_pays = options.taker_pays || options.gets;
if (!utils.isValidAmount0(taker_pays)) {
request.message.taker_pays = new Error("invalid taker pays amount");
return request;
}
options.limit = Number(options.limit);
if (!options.limit) {
options.limit = 300;
}
request.message.taker_gets = taker_gets;
request.message.taker_pays = taker_pays;
request.message.taker = options.taker
? options.taker
: Wallet.getAccountOne();
request.message.limit = options.limit;
return request;
}
requestBrokerage(options) {
const request = new Request(this, "Fee_Info");
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
const account = options.account;
if (!utils.isValidAddress(account)) {
request.message.account = new Error("account parameter is invalid");
return request;
}
request.message.account = account;
request.message.ledger_index = "validated";
return request;
}
requestSignerList(options) {
const request = new Request(this, "account_objects");
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
const account = options.account;
if (!utils.isValidAddress(account)) {
request.message.account = new Error("account parameter is invalid");
return request;
}
request.message.account = account;
return request;
}
requestPathFind(options) {
const request = new Request(this, "path_find", data => {
const request2 = new Request(this, "path_find");
request2.message.subcommand = "close";
request2.submit();
const _result = [];
for (const item of data.alternatives) {
const key = sha1(JSON.stringify(item));
this._paths.set(key, {
path: JSON.stringify(item.paths_computed),
choice: item.source_amount
});
_result.push({
choice: utils.parseAmount(item.source_amount),
key
});
}
return _result;
});
if (options === null || typeof options !== "object") {
request.message.type = new Error("invalid options type");
return request;
}
const account = options.account;
const dest = options.destination;
const amount = options.amount;
if (!utils.isValidAddress(account)) {
request.message.source_account = new Error("invalid source account");
return request;
}
if (!utils.isValidAddress(dest)) {
request.message.destination_account = new Error("invalid destination account");
return request;
}
if (!utils.isValidAmount(amount)) {
request.message.destination_amount = new Error("invalid amount");
return request;
}
request.message.subcommand = "create";
request.message.source_account = account;
request.message.destination_account = dest;
request.message.destination_amount = utils.ToAmount(amount);
return request;
}
buildPaymentTx(options) {
return Transaction.buildPaymentTx(options, this);
}
initContract(options) {
return Transaction.initContractTx(options, this);
}
invokeContract(options) {
return Transaction.invokeContractTx(options, this);
}
initContractTx(options) {
return Transaction.initContractTx(options, this);
}
invokeContractTx(options) {
return Transaction.invokeContractTx(options, this);
}
buildContractInitTx(options) {
return Transaction.initContractTx(options, this);
}
buildContractInvokeTx(options) {
return Transaction.invokeContractTx(options, this);
}
buildContractDeployTx(options) {
return Transaction.deployContractTx(options, this);
}
buildContractCallTx(options) {
return Transaction.callContractTx(options, this);
}
deployContractTx(options) {
return Transaction.deployContractTx(options, this);
}
callContractTx(options) {
return Transaction.callContractTx(options, this);
}
buildSignTx(options) {
return Transaction.buildSignTx(options, this);
}
buildBrokerageTx(options) {
return Transaction.buildBrokerageTx(options, this);
}
buildRelationTx(options) {
return Transaction.buildRelationTx(options, this);
}
buildAccountSetTx(options) {
return Transaction.buildAccountSetTx(options, this);
}
buildOfferCreateTx(options) {
return Transaction.buildOfferCreateTx(options, this);
}
buildOfferCancelTx(options) {
return Transaction.buildOfferCancelTx(options, this);
}
buildSignerListTx(options) {
return Transaction.buildSignerListTx(options, this);
}
buildSignFirstTx(options) {
return Transaction.buildSignFirstTx(options);
}
buildSignOtherTx(options) {
return Transaction.buildSignOtherTx(options, this);
}
buildMultisignedTx(tx_json) {
return Transaction.buildMultisignedTx(tx_json, this);
}
buildTx(tx_json) {
return Transaction.buildTx(tx_json, this);
}
subscribe(streams) {
const request = new Request(this, "subscribe");
if (streams) {
request.message.streams = Array.isArray(streams) ? streams : [streams];
}
return request;
}
unsubscribe(streams) {
const request = new Request(this, "unsubscribe");
if (streams) {
request.message.streams = Array.isArray(streams) ? streams : [streams];
}
return request;
}
createAccountStub() {
return new Account(this);
}
createOrderBookStub() {
return new OrderBook(this);
}
_handleLedgerClosed(data) {
if (data.ledger_index > this._status.ledger_index) {
this._status.ledger_index = data.ledger_index;
this._status.ledger_time = data.ledger_time;
this._status.reserve_base = data.reserve_base;
this._status.reserve_inc = data.reserve_inc;
this._status.fee_base = data.fee_base;
this._status.fee_ref = data.fee_ref;
this.emit("ledger_closed", data);
}
}
_handleServerStatus(data) {
this._updateServerStatus(data);
this.emit("server_status", data);
}
_updateServerStatus(data) {
this._status.load_base = data.load_base;
this._status.load_factor = data.load_factor;
if (data.pubkey_node) {
this._status.pubkey_node = data.pubkey_node;
}
this._status.server_status = data.server_status;
const online = ~Server.onlineStates.indexOf(data.server_status);
this._server._setState(online ? "online" : "offline");
}
_handleResponse(data) {
const req_id = data.id;
if (typeof req_id !== "number" ||
req_id < 0 ||
req_id > this._requests.length) {
return;
}
const request = this._requests[req_id];
if (request.data && request.data.abi) {
data.abi = request.data.abi;
}
delete this._requests[req_id];
delete data.id;
if (data.result && data.status === "success" && data.result.server_status) {
this._updateServerStatus(data.result);
}
if (this._solidity) {
if (data.status === "success") {
const result = request.filter(data.result);
if (result.ContractState &&
result.tx_json.TransactionType === "AlethContract" &&
result.tx_json.Method === 1) {
const method = utils.hexToString(result.tx_json.MethodSignature);
result.func = method.substring(0, method.indexOf("("));
result.func_parms = method
.substring(method.indexOf("(") + 1, method.indexOf(")"))
.split(",");
if (result.func_parms.length === 1 && result.func_parms[0] === "") {
result.func_parms = [];
}
if (result.engine_result === "tesSUCCESS") {
const abi = new this.AbiCoder();
const types = utils.getTypes(data.abi, result.func);
result.ContractState = abi.decodeParameters(types, result.ContractState);
types.forEach((type, i) => {
if (type === "address") {
const adr = result.ContractState[i].slice(2);
const buf = new Buffer(20);
buf.write(adr, 0, "hex");
result.ContractState[i] = Wallet.KeyPair.addressCodec.encodeAddress(buf);
}
});
}
}
if (result.AlethLog) {
const logValue = [];
const item = { address: "", data: {} };
const logs = result.AlethLog;
logs.forEach(log => {
const _log = JSON.parse(log.item);
const _adr = _log.address.slice(2);
const buf = new Buffer(20);
buf.write(_adr, 0, "hex");
item.address = Wallet.KeyPair.addressCodec.encodeAddress(buf);
const abi = new this.AbiCoder();
data.abi
.filter(json => {
return json.type === "event";
})
.map(json => {
const types = json.inputs.map(input => {
return input.type;
});
const foo = json.name + "(" + types.join(",") + ")";
if (abi.encodeEventSignature(foo) === _log.topics[0]) {
const data2 = abi.decodeLog(json.inputs, _log.data, _log.topics);
json.inputs.forEach((input, i) => {
if (input.type === "address") {
const _adr2 = data2[i].slice(2);
const buf2 = new Buffer(20);
buf2.write(_adr2, 0, "hex");
item.data[i] = Wallet.KeyPair.addressCodec.encodeAddress(buf2);
}
else {
item.data[i] = data2[i];
}
});
}
});
logValue.push(item);
});
result.AlethLog = logValue;
}
request && request.callback(null, result);
}
else if (data.status === "error") {
request && request.callback(data.error_message || data.error_exception);
}
}
else {
if (data.status === "success") {
const result = request.filter(data.result);
request && request.callback(null, result);
}
else if (data.status === "error") {
request && request.callback(data.error_message || data.error_exception);
}
}
}
_handleTransaction(data) {
const tx = data.transaction.hash;
if (this._cache.get(tx))
return;
this._cache.set(tx, 1);
this.emit("transactions", data);
}
_handlePathFind(data) {
this.emit("path_find", data);
}
__requestAccount(type, options, request) {
request._command = type;
const account = options.account;
const ledger = options.ledger;
const peer = options.peer;
let limit = options.limit;
const marker = options.marker;
request.message.relation_type = getRelationType(options.type);
if (account) {
if (!utils.isValidAddress(account)) {
request.message.account = new Error("invalid account");
return request;
}
else {
request.message.account = account;
}
}
request.selectLedger(ledger);
if (peer && utils.isValidAddress(peer)) {
request.message.peer = peer;
}
if (Number(limit)) {
limit = Number(limit);
if (limit < 0)
limit = 0;
if (limit > 1e9)
limit = 1e9;
request.message.limit = limit;
}
if (marker) {
request.message.marker = marker;
}
return request;
}
}
Remote.Wallet = Wallet;
Remote.Account = Account;
Remote.OrderBook = OrderBook;
Remote.Transaction = Transaction;
Remote.utils = utils;
Remote.XLIB = Wallet.config.XLIB || {};
export { Remote };
//# sourceMappingURL=remote.js.map