UNPKG

jingtum-lib

Version:

jingtum lib

1,626 lines (1,496 loc) 60.6 kB
'use strict'; var Event = require('events').EventEmitter; var util = require('util'); var LRU = require('lru-cache'); var sha1 = require('sha1'); var utf8 = require('utf8'); var Server = require('./server'); var Request = require('./request'); var Account = require('./account'); var Transaction = require('./transaction'); var OrderBook = require('./orderbook'); var utils = require('./utils'); var _ = require('lodash'); const currency = require('./config').currency; const fee = require('./config').fee; var bignumber = require('bignumber.js'); var Tum3 = require('tum3'); var AbiCoder = require('tum3-eth-abi').AbiCoder; var KeyPair = require('jingtum-base-lib').KeyPair; var LEDGER_OPTIONS = ['closed', 'header', 'current']; /** * main handler for backend system * one remote object one server, not many * options onfiguration Parameters: * { * local_sign: false, // default sign tx in jingtumd * server: 'wss://s.jingtum.com:5020', // only support one server * } * @param options * @constructor */ function Remote(options) { Event.call(this); var self = this; var _opts = options || {}; self._local_sign = !!_opts.local_sign; if (typeof _opts.server !== 'string') { self.type = new TypeError('server config not supplied'); return self; } self._url = _opts.server; self._server = new Server(self, self._url); self._status = {ledger_index: 0}; self._requests = {}; self._cache = LRU({max: 100, maxAge: 1000 * 60 * 5}); // 100 size, 5 min self._paths = LRU({max: 100, maxAge: 1000 * 60 * 5}); // 2100 size, 5 min self.on('newListener', function(type, listener) { if (!self._server.isConnected()) return; if (type === 'removeListener') return; if (type === 'transactions') { self.subscribe('transactions').submit(); } if (type === 'ledger_closed') { self.subscribe('ledger').submit(); } }); self.on('removeListener', function(type) { if (!self._server.isConnected()) return; if (type === 'transactions') { self.unsubscribe('transactions').submit(); } if (type === 'ledger_closed') { self.unsubscribe('ledger').submit(); } }); } util.inherits(Remote, Event); /** * connect first on every case * callback(error, result) * @param callback * @returns {*} */ Remote.prototype.connect = function(callback) { if (!this._server) return callback('server not ready'); this._server.connect(callback); }; /** * disconnect manual, no reconnect */ Remote.prototype.disconnect = function() { if (!this._server) return; this._server.disconnect(); }; /** * check is remote is connected to jingtumd */ Remote.prototype.isConnected = function () { return this._server.isConnected(); }; /** * handle message from backend, and dispatch * @param data * @private */ Remote.prototype._handleMessage = function(data) { var self = this; try { data = JSON.parse(data); } catch(e) {} if (typeof data !== 'object') return; switch(data.type) { case 'ledgerClosed': self._handleLedgerClosed(data); break; case 'serverStatus': self._handleServerStatus(data); break; case 'response': self._handleResponse(data); break; case 'transaction': self._handleTransaction(data); break; case 'path_find': self._handlePathFind(data); break; } }; /** * update server ledger status * TODO * supply data to outside include ledger, reserve and fee * @param data * @private */ Remote.prototype._handleLedgerClosed = function(data) { var self = this; if (data.ledger_index > self._status.ledger_index) { self._status.ledger_index = data.ledger_index; self._status.ledger_time = data.ledger_time; self._status.reserve_base = data.reserve_base; self._status.reserve_inc = data.reserve_inc; self._status.fee_base = data.fee_base; self._status.fee_ref = data.fee_ref; self.emit('ledger_closed', data); } }; /** * TODO * supply data to outside about server status * @param data * @private */ Remote.prototype._handleServerStatus = function(data) { // TODO check data format this._updateServerStatus(data); this.emit('server_status', data); }; /** * update remote state and server state * @param data * @private */ Remote.prototype._updateServerStatus = function(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; var online = ~Server.onlineStates.indexOf(data.server_status); this._server._setState(online ? 'online' : 'offline'); }; function getTypes(abi, foo) { return abi.filter(function (json) { return json.name === foo }).map(function (json) { return json.outputs.map(function (input) { return input.type; }); }).map(function (types) { return types; })[0] || ''; } /** * handle response by every websocket request * @param data * @private */ Remote.prototype._handleResponse = function(data) { var req_id = data.id; if (typeof req_id !== 'number' || req_id < 0 || req_id > this._requests.length) { return; } var request = this._requests[req_id]; // pass process it when null callback if(request.data && request.data.abi){ data.abi = request.data.abi; } delete this._requests[req_id]; delete data.id; // check if data contain server info if (data.result && data.status === 'success' && data.result.server_status) { this._updateServerStatus(data.result); } // return to callback if (data.status === 'success') { var result = request.filter(data.result); if(result.ContractState && result.tx_json.TransactionType === 'AlethContract' && result.tx_json.Method === 1){//调用合约时,如果是获取变量,则转换一下 var 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'){ var abi = new AbiCoder(); var types = getTypes(data.abi, result.func); result.ContractState = abi.decodeParameters(types, result.ContractState); types.forEach(function (type, i) { if(type === 'address') { var adr = result.ContractState[i].slice(2); var buf = new Buffer(20); buf.write(adr, 0, 'hex'); result.ContractState[i] = KeyPair.__encode(buf) } }); } } if(result.AlethLog){ var logValue = []; var item = {address: '', data: {}}; var logs = result.AlethLog; logs.forEach(function (log) { var _log = JSON.parse(log.item); var _adr = _log.address.slice(2); var buf = new Buffer(20); buf.write(_adr, 0, 'hex'); item.address = KeyPair.__encode(buf); var abi = new AbiCoder(); data.abi.filter(function (json) { return json.type === 'event' }) .map(function (json) { var types = json.inputs.map(function (input) { return input.type; }); var foo = json.name + '(' + types.join(',') + ')'; if(abi.encodeEventSignature(foo) === _log.topics[0]){ var data = abi.decodeLog(json.inputs, _log.data, _log.topics); json.inputs.forEach(function (input, i) { if(input.type === 'address'){ var _adr = data[i].slice(2); var buf = new Buffer(20); buf.write(_adr, 0, 'hex'); item.data[i] = KeyPair.__encode(buf); }else { item.data[i] = data[i]; } }); } }); logValue.push(item); }); result.AlethLog = logValue; } if(result.TransactionType === 'SetBlackList' || result.TransactionType === 'RemoveBlackList'){ //该类型实际未收燃料费 result.Fee = '0'; } request && request.callback(null, result); } else if (data.status === 'error') { request && request.callback(data.error_message || data.error_exception || data.error); } }; /** * handle transaction type response * TODO supply more friendly transaction data * @param data * @private */ Remote.prototype._handleTransaction = function(data) { var self = this; var tx = data.transaction.hash; if (self._cache.get(tx)) return; self._cache.set(tx, 1); this.emit('transactions', data); }; /** * emit path find date to other * TODO supply more friendly data * @param data * @private */ Remote.prototype._handlePathFind = function(data) { this.emit('path_find', data); }; /** * request to server and backend * @param command * @param data * @param filter * @param callback * @private */ Remote.prototype._submit = function(command, data, filter, callback) { if (!callback || typeof callback !== 'function') { callback = function() {}; } var req_id = this._server.sendMessage(command, data); this._requests[req_id] = { command: command, data: data, filter: filter, callback: callback }; }; // ---------------------- info request -------------------- /** * request server info * return version, ledger, state and node id * no option is required * @returns {Request} */ Remote.prototype.requestServerInfo = function() { return new Request(this, 'server_info', function(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 }; }); }; /** * request peers info * return version, ledger, state and node id * no option is required * @returns {Request} */ Remote.prototype.requestPeers = function() { return new Request(this, 'peers', function(data) { return data; }); }; /** * request last closed ledger index and hash * @returns {Request} */ Remote.prototype.requestLedgerClosed = function () { return new Request(this, 'ledger_closed', function(data) { return { // fee_base: data.fee_base, ledger_hash: data.ledger_hash, ledger_index: data.ledger_index, // reserve_base: data.reserve_base, // reserve_inc: data.reserve_base, // txn_count: data.txn_count, // validated: data.validated_ledgers }; }); }; /** * get one ledger info * options parameters : { * ledger_index: Number, * ledger_hash: hash, string * } * if no options, return last closed ledger * @param options * @returns {Request} */ Remote.prototype.requestLedger = function(options) { // if (typeof options !== 'object') { // return new Error('invalid options type'); // } var cmd = 'ledger'; var filter = true; var request = new Request(this, cmd, function(data) { var 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(options.ledger_index)) {//支持0-10位数字查询 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; }; /* * get all accounts at some ledger_index * */ Remote.prototype.requestAccounts = function(options) { var 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(options.ledger_index)) {//支持0-10位数字查询 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; }; /** * for tx command * @param options * options: { * hash: tx hash, string * } * @returns {Request} */ Remote.prototype.requestTx = function(options) { var request = new Request(this, 'tx'); if (typeof options !== 'object') { request.message.type = new Error('invalid options type'); return request; } var hash = options.hash; if (!utils.isValidHash(hash)) { request.message.hash = new Error('invalid tx hash'); return request; } request.message.transaction = hash; return request; }; function getRelationType(type) { switch (type) { case 'trustline': return 0; break; case 'authorize': return 1; break; case 'freeze': return 3; break; default: return null; } } /** * request account info, internal function * @param type * @param options * @returns {Request} * @private */ Remote.prototype.__requestAccount = function(type, options, request, filter) { // var request = new Request(this, type, filter); request._command = type; var account = options.account; var ledger = options.ledger; var peer = options.peer; var limit = options.limit; var marker = options.marker; // if (marker && (Number(ledger) <= 0 || !utils.isValidHash(ledger))) { // throw new Error('marker needs a ledger_index or ledger_hash'); // } 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; }; /** * account info * @param options, options: * account(required): the query account * ledger(option): specify ledger, ledger can be: * ledger_index=xxx, ledger_hash=xxx, or ledger=closed|current|validated * @returns {Request} */ Remote.prototype.requestAccountInfo = function(options) { var 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); }; /** * account tums * return account supports currency, including * send currency and receive currency * @param * account(required): the query account * ledger(option): specify ledger, ledger can be: * ledger_index=xxx, ledger_hash=xxx, or ledger=closed|current|validated * no limit * @returns {Request} */ Remote.prototype.requestAccountTums = function(options) { var 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); }; /** * account relations * @param options * type: relation type * account(required): the query account * ledger(option): specify ledger, ledger can be: * ledger_index=xxx, ledger_hash=xxx, or ledger=closed|current|validated * limit min is 200, * marker for more relations * @returns {Request} */ Remote.prototype.requestAccountRelations = function(options) { var 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; }; /** * account offers * options parameters * @param options * account(required): the query account * ledger(option): specify ledger, ledger can be: * ledger_index=xxx, ledger_hash=xxx, or ledger=closed|current|validated * limit min is 200, marker * @returns {Request} */ Remote.prototype.requestAccountOffers = function(options) { var 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); }; /** * account tx * options parameters * account(required): the query account * ledger(option): specify ledger, ledger can be: * ledger_index=xxx, ledger_hash=xxx, or ledger=closed|current|validated * limit limit output tx record * ledger_min default 0, ledger_max default -1 * marker: {ledger:xxx, seq: x} * descending, if returns recently tx records * @returns {Request} */ Remote.prototype.requestAccountTx = function(options) { var request = new Request(this, 'account_tx', function(data) { var results = []; for (var i = 0; i < data.transactions.length; ++i) { var _tx = utils.processTx(data.transactions[i], options.account); results.push(_tx); } 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(options.marker.ledger) !== NaN && Number(options.marker.seq) !== NaN) { request.message.marker = options.marker; } if(options.forward && typeof options.forward === 'boolean'){//true 正向;false反向 request.message.forward = options.forward; } return request; }; /** * request order book, * options {gets: {currency: , issuer: }, pays: {currency: ', issuer: '}} * for order pair AAA/BBB * to get bids, gets=AAA, pays=BBB * to get asks, gets=BBB, pays=AAA * for bids orders are ordered by price desc * for asks orders are ordered by price asc * TODO format data * @param options * @returns {Request} */ Remote.prototype.requestOrderBook = function(options) { var request = new Request(this, 'book_offers'); if (options === null || typeof options !== 'object') { request.message.type = new Error('invalid options type'); return request; } var taker_gets = options.taker_gets || options.pays; if (!utils.isValidAmount0(taker_gets)) { request.message.taker_gets = new Error('invalid taker gets amount'); return request; } var taker_pays = options.taker_pays || options.gets; if (!utils.isValidAmount0(taker_pays)) { request.message.taker_pays = new Error('invalid taker pays amount'); return request; } if (_.isNumber(options.limit)) { options.limit = parseInt(options.limit); } request.message.taker_gets = taker_gets; request.message.taker_pays = taker_pays; request.message.taker = options.taker ? options.taker : utils.ACCOUNT_ONE; request.message.limit = options.limit; return request; }; /* * request brokerage, * @param options * @returns {Request} * */ Remote.prototype.requestBrokerage = function(options) { var request = new Request(this, 'Fee_Info'); if (options === null || typeof options !== 'object') { request.message.type = new Error('invalid options type'); return request; } var 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; }; Remote.prototype.requestSignerList = function(options) { var request = new Request(this, 'account_objects', function (data) { var _result = []; data.account_objects.forEach(function (o) { if(o.LedgerEntryType === 'SignerList') _result.push(o); }); data.account_objects = _result; return data; }); if (options === null || typeof options !== 'object') { request.message.type = new Error('invalid options type'); return request; } var account = options.account; if (!utils.isValidAddress(account)) { request.message.account = new Error('account parameter is invalid'); return request; } request.message.account = account; return request; }; /** * @param options * { * account(option): the query account * marker(option): for more black account * } * @returns {Request} */ Remote.prototype.requestBlacklist = function(options) { var request = new Request(this, 'blacklist_info'); if(!options){ return request; } if (options && typeof options !== 'object') { request.message.type = new Error('invalid options type'); return request; } var account = options.account; if(account && !utils.isValidAddress(account)){ request.message.account = new Error('invalid account'); return request; } if (options.marker) { request.message.marker = options.marker; } request.message.account = account; return request; }; // ---------------------- path find request -------------------- /** * @param options * { * account: acccount|from|source, account to find path * destination: destination|to|dst, destiantion account * amount: the amount destination will received * } * @returns {Request} */ Remote.prototype.requestPathFind = function(options) { var self = this; var request = new Request(self, 'path_find', function(data) { var request2 = new Request(self, 'path_find'); request2.message.subcommand = 'close'; request2.submit(); var _result = []; for (var i = 0; i < data.alternatives.length; ++i) { var item = data.alternatives[i]; var key = sha1(JSON.stringify(item)); self._paths.set(key, { path: JSON.stringify(item.paths_computed), choice: item.source_amount }); _result.push({ choice: utils.parseAmount(item.source_amount), key: key }); } return _result; }); if (options === null || typeof options !== 'object') { request.message.type = new Error('invalid options type'); return request; } var account = options.account; var dest = options.destination; var 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 = ToAmount(amount); return request; }; // ---------------------- subscribe -------------------- /** * @param streams * @returns {Request} */ Remote.prototype.subscribe = function(streams) { var request = new Request(this, 'subscribe'); if (streams) { request.message.streams = Array.isArray(streams) ? streams : [streams]; } return request; }; /** * @param streams * @returns {Request} */ Remote.prototype.unsubscribe = function(streams) { var request = new Request(this, 'unsubscribe'); if (streams) { request.message.streams = Array.isArray(streams) ? streams : [streams]; } return request; }; /** * stub function for account event * @returns {Account} */ Remote.prototype.createAccountStub = function() { return new Account(this); }; /** stub function for order book * * @returns {OrderBook} */ Remote.prototype.createOrderBookStub = function() { return new OrderBook(this); }; // ---------------------- transaction request -------------------- /** * return string if swt amount * @param amount * @returns {Amount} */ function ToAmount(amount) { if(amount.value && Number(amount.value) > 100000000000){ return new Error('invalid amount: amount\'s maximum value is 100000000000'); } if (amount.currency === currency) { // return new String(parseInt(Number(amount.value) * 1000000.00)); return String(parseInt(new bignumber(amount.value).multipliedBy(1000000.00))); } return amount; } /** * payment * @param options * source|from|account source account, required * destination|to destination account, required * amount payment amount, required * @returns {Transaction} */ Remote.prototype.buildPaymentTx = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } var src = options.source || options.from || options.account; var dst = options.destination || options.to; var amount = options.amount; if (!utils.isValidAddress(src)) { tx.tx_json.src = new Error('invalid source address'); return tx; } if (!utils.isValidAddress(dst)) { tx.tx_json.dst = new Error('invalid destination address'); return tx; } if (!utils.isValidAmount(amount)) { tx.tx_json.amount = new Error('invalid amount'); return tx; } tx.tx_json.TransactionType = 'Payment'; tx.tx_json.Account = src; tx.tx_json.Amount = ToAmount(amount); tx.tx_json.Destination = dst; return tx }; Remote.prototype.initContract = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } var account = options.account; var amount = options.amount; var payload = options.payload; var params = options.params || []; var abi = options.abi; if (!utils.isValidAddress(account)) { tx.tx_json.account = new Error('invalid address'); return tx; } if (isNaN(amount)) { tx.tx_json.amount = new Error('invalid amount'); return tx; } if(typeof payload !== 'string'){ tx.tx_json.payload = new Error('invalid payload: type error.'); return tx; } if (!Array.isArray(params)) { tx.tx_json.params = new Error('invalid params: type error.'); return tx; } if (!abi) { tx.tx_json.abi = new Error('not found abi'); return tx; } if (!Array.isArray(abi)) { tx.tx_json.params = new Error('invalid abi: type error.'); return tx; } var tum3 = new Tum3(); tum3.mc.defaultAccount = account; var MyContract = tum3.mc.contract(abi); var contractData = MyContract.new.getData.apply(null, params.concat({data: payload})); tx.tx_json.TransactionType = 'AlethContract'; tx.tx_json.Account = account; tx.tx_json.Amount = Number(amount) * 1000000; tx.tx_json.Method = 0; tx.tx_json.Payload = utils.stringToHex(contractData); return tx }; Remote.prototype.invokeContract = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } var account = options.account; var des = options.destination; var func = options.func; //函数名及函数参数 var abi = options.abi; var amount = options.amount; if (!utils.isValidAddress(account)) { tx.tx_json.account = new Error('invalid address'); return tx; } if (!utils.isValidAddress(des)) { tx.tx_json.des = new Error('invalid destination'); return tx; } if(typeof func !== 'string' || func.indexOf('(') < 0 || func.indexOf(')') < 0){ tx.tx_json.func = new Error('invalid func, func must be string'); return tx; } if (!abi) { tx.tx_json.abi = new Error('not found abi'); return tx; } if (!Array.isArray(abi)) { tx.tx_json.params = new Error('invalid abi: type error.'); return tx; } if(amount && isNaN(amount)){ tx.tx_json.amount = new Error('invalid amount: amount must be a number.'); return tx; } if(amount){ abi.forEach(function (a) { if(a.name === func.substring(0, func.indexOf('(')) && !a.payable){ tx.tx_json.amount = new Error('when payable is true, you can set the value of amount'); return tx; } }) } var tum3 = new Tum3(); tum3.mc.defaultAccount = account; var MyContract = tum3.mc.contract(abi); tx.abi = abi; var myContractInstance = MyContract.at(des);// initiate contract for an address try { var result = eval('myContractInstance.' + func);// call constant function }catch (e){ console.log('not found this function.' + e); } if(!result){ tx.tx_json.des = new Error('invalid func, no result'); return tx; } tx.tx_json.TransactionType = 'AlethContract'; tx.tx_json.Account = account; tx.tx_json.Method = 1; tx.tx_json.Destination = des; tx.tx_json.Amount = options.amount ? options.amount: 0; tx.tx_json.MethodSignature = utils.stringToHex(func); tx.tx_json.Args = []; tx.tx_json.Args.push({Arg: {Parameter: utils.stringToHex(result.substr(2,result.length)), ContractParamsType:0}}); return tx; }; Remote.prototype.AlethEvent = function(options) { var request = new Request(this, 'aleth_eventlog', function(data) { return data; }); if (typeof options !== 'object') { request.message.obj = new Error('invalid options type'); return request; } var des = options.destination; var 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; }; /** * contract * @param options * account, required * amount, required * payload, required * @returns {Transaction} */ Remote.prototype.deployContractTx = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } var account = options.account; var amount = options.amount; var payload = options.payload; var params = options.params; if (!utils.isValidAddress(account)) { tx.tx_json.account = new Error('invalid address'); return tx; } if (isNaN(amount)) { tx.tx_json.amount = new Error('invalid amount'); return tx; } if(typeof payload !== 'string'){ tx.tx_json.payload = new Error('invalid payload: type error.'); return tx; } if (params && !Array.isArray(params)) { tx.tx_json.params = new Error('invalid options type'); return tx; } tx.tx_json.TransactionType = 'ConfigContract'; tx.tx_json.Account = account; tx.tx_json.Amount = Number(amount) * 1000000; tx.tx_json.Method = 0; tx.tx_json.Payload = payload; tx.tx_json.Args = []; for(var i in params){ var obj = {}; obj.Arg = {Parameter : utils.stringToHex(params[i])}; tx.tx_json.Args.push(obj); } return tx }; /** * contract * @param options * account, required * des, required * params, required * @returns {Transaction} */ Remote.prototype.callContractTx = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } var account = options.account; var des = options.destination; var params = options.params; var func = options.func; //函数名 if (!utils.isValidAddress(account)) { tx.tx_json.account = new Error('invalid address'); return tx; } if (!utils.isValidAddress(des)) { tx.tx_json.des = new Error('invalid destination'); return tx; } if (params && !Array.isArray(params)) { tx.tx_json.params = new Error('invalid options type'); return tx; } if(typeof func !== 'string'){ tx.tx_json.func = new Error('func must be string'); return tx; } tx.tx_json.TransactionType = 'ConfigContract'; tx.tx_json.Account = account; tx.tx_json.Method = 1; tx.tx_json.ContractMethod = utils.stringToHex(func); tx.tx_json.Destination = des; tx.tx_json.Args = []; for(var i in params){ if(typeof params[i] !== 'string'){ tx.tx_json.params = new Error('params must be string'); return tx; } var obj = {}; obj.Arg = {Parameter : utils.stringToHex(params[i])}; tx.tx_json.Args.push(obj); } return tx; }; Remote.prototype.buildSignTx = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } tx.tx_json.TransactionType = 'Signer'; tx.tx_json.blob = options.blob; return tx; }; /** * Brokerage 设置挂单手续费 * @param options * account, required * mol|molecule, required * den|denominator, required * app, required * amount, required * @returns {Transaction} */ Remote.prototype.buildBrokerageTx = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } var account = options.account; var feeAccount = options.feeAccount; var mol = (Number(options.mol) === 0 || Number(options.molecule) === 0) ? 0 : (options.mol || options.molecule); var den = options.den || options.denominator; var amount = options.amount; if (!utils.isValidAddress(account)) { tx.tx_json.src = new Error('invalid address'); return tx; } if(!/^\d+$/.test(mol)){//(正整数 + 0) tx.tx_json.mol = new Error('invalid mol, it is a positive integer or zero.'); return tx; } if(Number(mol) > Number(den)){ tx.tx_json.app = new Error('invalid mol/den, molecule can not exceed denominator.'); return tx; } if (!utils.isValidAmount(amount)) { tx.tx_json.amount = new Error('invalid amount'); return tx; } tx.tx_json.TransactionType = 'Brokerage'; tx.tx_json.Account = account; //管理员账号 tx.tx_json.OfferFeeRateNum = Number(mol); //分子(正整数 + 0) tx.tx_json.OfferFeeRateDen = Number(den); //分母(正整数) tx.tx_json.Amount = ToAmount(amount); //币种,这里amount字段中的value值只是占位,没有实际意义。 tx.tx_json.FeeAccountID = feeAccount; //收费账号 return tx; }; Remote.prototype.__buildTrustSet = function(options, tx) { // var tx = new Transaction(this); // if (typeof options !== 'object') { // tx.tx_json.obj = new Error('invalid options type'); // return tx; // } var src = options.source || options.from || options.account; var limit = options.limit; var quality_out = options.quality_out; var quality_in = options.quality_in; if (!utils.isValidAddress(src)) { tx.tx_json.src = new Error('invalid source address'); return tx; } if (!utils.isValidAmount(limit)) { tx.tx_json.limit = new Error('invalid amount'); return tx; } tx.tx_json.TransactionType = 'TrustSet'; tx.tx_json.Account = src; if (limit !== void(0)) { tx.tx_json.LimitAmount = limit; } if (quality_in) { tx.tx_json.QualityIn = quality_in; } if (quality_out) { tx.tx_json.QualityOut = quality_out; } return tx; }; Remote.prototype.__buildRelationSet = function(options, tx) { // TODO // var tx = new Transaction(this); // if (typeof options !== 'object') { // tx.tx_json.obj = new Error('invalid options type'); // return tx; // } var src = options.source || options.from || options.account; var des = options.target; var limit = options.limit; if (!utils.isValidAddress(src)) { tx.tx_json.src = new Error('invalid source address'); return tx; } if (!utils.isValidAddress(des)) { tx.tx_json.des = new Error('invalid target address'); return tx; } if (!utils.isValidAmount(limit)) { tx.tx_json.limit = new Error('invalid amount'); return tx; } tx.tx_json.TransactionType = options.type === 'unfreeze' ? 'RelationDel' : 'RelationSet'; tx.tx_json.Account = src; tx.tx_json.Target = des; tx.tx_json.RelationType = options.type === 'authorize' ? 1 : 3; if (limit !== void(0)) { tx.tx_json.LimitAmount = limit; } return tx; }; /** * add wallet relation set * @param options * type: Transaction.RelationTypes * source|from|account source account, required * limit limt amount, required * quality_out, optional * quality_in, optional * @returns {Transaction} */ Remote.prototype.buildRelationTx = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } if (!~Transaction.RelationTypes.indexOf(options.type)) { tx.tx_json.type = new Error('invalid relation type'); return tx; } switch (options.type) { case 'trust': return this.__buildTrustSet(options, tx); case 'authorize': case 'freeze': case 'unfreeze': return this.__buildRelationSet(options, tx); } tx.tx_json.msg = new Error('build relation set should not go here'); return tx; }; /** * account information set * @param options * set_flag, flags to set * clear_flag, flags to clear * @returns {Transaction} */ Remote.prototype.__buildAccountSet = function(options, tx) { // var tx = new Transaction(this); // if (typeof options !== 'object') { // tx.tx_json.obj = new Error('invalid options type'); // return tx; // } var src = options.source || options.from || options.account; var set_flag = options.set_flag || options.set; var clear_flag = options.clear_flag || options.clear; if (!utils.isValidAddress(src)) { tx.tx_json.src = new Error('invalid source address'); return tx; } tx.tx_json.TransactionType= 'AccountSet'; tx.tx_json.Account = src; var SetClearFlags = Transaction.set_clear_flags.AccountSet; function prepareFlag(flag) { return (typeof flag === 'number') ? flag : (SetClearFlags[flag] || SetClearFlags['asf' + flag]); } if (set_flag && (set_flag = prepareFlag(set_flag))) { tx.tx_json.SetFlag = set_flag; } if (clear_flag && (clear_flag = prepareFlag(clear_flag))) { tx.tx_json.ClearFlag = clear_flag; } return tx; }; /** * delegate key setting * @param options * source|account|from, source account, required * delegate_key, delegate account, required * @returns {Transaction} */ Remote.prototype.__buildDelegateKeySet = function(options, tx) { // var tx = new Transaction(this); // if (typeof options !== 'object') { // tx.tx_json.obj = new Error('invalid options type'); // return tx; // } var src = options.source || options.account || options.from; var delegate_key = options.delegate_key; if (!utils.isValidAddress(src)) { tx.tx_json.src = new Error('invalid source address'); return tx; } if (!utils.isValidAddress(delegate_key)) { tx.tx_json.delegate_key = new Error('invalid regular key address'); return tx; } tx.tx_json.TransactionType = 'SetRegularKey'; tx.tx_json.Account = src; tx.tx_json.RegularKey = delegate_key; return tx; }; Remote.prototype.__buildSignerSet = function(options, tx) { // TODO return null; }; /** * account information set * @param options * type: Transaction.AccountSetTypes * @returns {Transaction} */ Remote.prototype.buildAccountSetTx = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } if (Transaction.AccountSetTypes.indexOf(options.type) === -1) { tx.tx_json.type = new Error('invalid account set type'); return tx; } switch(options.type) { case 'property': return this.__buildAccountSet(options, tx); case 'delegate': return this.__buildDelegateKeySet(options, tx); case 'signer': return this.__buildSignerSet(options, tx); } tx.tx_json.msg = new Error('build account set should not go here'); return tx; }; /** * offer create * @param options * type: 'Sell' or 'Buy' * source|from|account maker account, required * taker_gets|pays amount to take out, required * taker_pays|gets amount to take in, required * @returns {Transaction} */ Remote.prototype.buildOfferCreateTx = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } var offer_type = options.type; var src = options.source || options.from || options.account; var taker_gets = options.taker_gets || options.pays; var taker_pays = options.taker_pays || options.gets; var platform = options.platform;//app平台标识账号 if (!utils.isValidAddress(src)) { tx.tx_json.src = new Error('invalid source address'); return tx; } if (typeof offer_type !== 'string' || !~Transaction.OfferTypes.indexOf(offer_type)) { tx.tx_json.offer_type = new Error('invalid offer type'); return tx; } if (typeof taker_gets === 'string' && !Number(taker_gets)) { tx.tx_json.taker_gets = new Error('invalid to pays amount'); return tx; } if (typeof taker_gets === 'object' && !utils.isValidAmount(taker_gets)) { tx.tx_json.taker_gets = new Error('invalid to pays amount object'); return tx; } if (typeof taker_pays === 'string' && !Number(taker_pays)) { tx.tx_json.taker_pays = new Error('invalid to gets amount'); return tx; } if (typeof taker_pays === 'object' && !utils.isValidAmount(taker_pays)) { tx.tx_json.taker_pays = new Error('invalid to gets amount object'); return tx; } if(platform && !utils.isValidAddress(platform)) { tx.tx_json.platform = new Error('invalid platform, it must be a valid address.'); return tx; } tx.tx_json.TransactionType = 'OfferCreate'; if (offer_type === 'Sell') tx.setFlags(offer_type); if(platform) tx.tx_json.Platform = platform; tx.tx_json.Account = src; tx.tx_json.TakerPays = typeof taker_pays === 'object' ? ToAmount(taker_pays) : taker_pays; tx.tx_json.TakerGets = typeof taker_gets === 'object' ? ToAmount(taker_gets) : taker_gets; return tx; }; /** * offer cancel * @param options * source|from|account source account, required * sequence, required * @returns {Transaction} */ Remote.prototype.buildOfferCancelTx = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } var src = options.source || options.from || options.account; var sequence = options.sequence; if (!utils.isValidAddress(src)) { tx.tx_json.src = new Error('invalid source address'); return tx; } if (!Number(sequence)) { tx.tx_json.sequence = new Error('invalid sequence param'); return tx; } tx.tx_json.TransactionType = 'OfferCancel'; tx.tx_json.Account = src; tx.tx_json.OfferSequence = Number(sequence); return tx; }; Remote.prototype.buildSignerListTx = function(options) { var tx = new Transaction(this); if (options === null || typeof options !== 'object') { tx.tx_json.obj = new Error('invalid options type'); return tx; } var account = options.account; var threshold = options.threshold;//阈值,为0表示删除列表 var lists = options.lists; //签字人列表 if (!utils.isValidAddress(account)) { tx.tx_json.src = new Error('invalid address'); return tx; } if (isNaN(threshold) || Number(threshold) < 0) { tx.tx_json.threshold = new Error('invalid threshold, it must be a number and greater than zero'); return tx; } if (lists && !Array.isArray(lists)) { tx.tx_json.lists = new Error('invalid options type, it must be an array'); return tx; } if(Number(threshold) === 0 && lists && lists.length >= 0){ tx.tx_json.lists = new Er