UNPKG

openchain-sdk-yxl

Version:

openchain sdk

897 lines (769 loc) 23.4 kB
'use strict'; const axios = require('axios'); const is = require('is-type-of'); const long = require('long'); const JSONbig = require('json-bigint'); // const bigNumberToString = require('bignumber-to-string') const BigNumber = require('bignumber.js'); const protobuf = require("protobufjs"); const tou8 = require('buffer-to-uint8array'); const humps = require('humps'); const { keypair, signature } = require('openchain-encryption-nodejs-yxl'); const errors = require('../exception'); const proto = exports; /** * GET/POST request * * @param {String} method * @param {String} path * @param {Object} data * @return {Object} */ // 替换原有request实现 proto._request = async function (method, endpoint, paramsAndData) { let params = {}; let data = {}; if (method === 'get') { params = paramsAndData } else { data = paramsAndData } try { const response = await axios({ method, url: `${this.options.host}/${endpoint}`, params, data, }); return response.data; } catch (error) { throw this._handleRequestError(error); } }; proto._response = function (obj) { const data = { errorCode: obj.error_code || 0, errorDesc: obj.error_desc || 'Success', }; if (is.object(obj) && obj.error_code) { if (obj.error_code === 0) { data.result = obj.result || {}; } else { data.errorDesc = obj.error_desc || ''; data.result = {}; } } else { data.result = obj; } return JSONbig.stringify(data); }; proto._getBlockNumber = function* () { try { const data = yield this._request('get', 'getLedger'); if (data && data.error_code === 0) { const seq = data.result.header.seq; return this._responseData({ header: { blockNumber: seq, }, }); } else { return this._responseError(errors.INTERNAL_ERROR); } } catch (err) { throw err; } }; proto._isEmptyString = function (str) { if (!is.string(str)) { throw new Error('str must be a string'); } return (str.trim().length === 0); }; proto._postData = function (blob, signature) { const data = { items: [ { transaction_blob: blob, signatures: signature }, ], }; return JSONbig.stringify(data); }; proto._isBigNumber = function (object) { return object instanceof BigNumber || (object && object.constructor && object.constructor.name === 'BigNumber'); }; proto._toBigNumber = function (number) { number = number || 0; // if (this._isBigNumber(number)) { return number; } return new BigNumber(number); }; proto._stringFromBigNumber = function (number) { return this._toBigNumber(number).toString(10); }; proto._verifyValue = function (str) { const reg = /^[1-9]\d*$/; return ( is.string(str) && reg.test(str) && long.fromValue(str).greaterThan(0) && long.fromValue(str).lessThanOrEqual(long.MAX_VALUE) ); }; proto._isAvailableValue = function (str, from = -1, to = long.MAX_VALUE) { const reg = /^(0|([1-9]\d*))$/ return ( is.string(str) && reg.test(str) && long.fromValue(str).greaterThan(from) && long.fromValue(str).lessThanOrEqual(to) ); }; proto._checkParams = function (obj) { for (let prop in obj) { if (obj.hasOwnProperty(prop)) { let value = obj[prop]; if (!is.undefined(value)) { if (!this._verifyValue(value)) { throw new Error(errors.INVALID_FORMAT_OF_ARG.msg); } } } } }; proto._getDefaultValue = function* () { try { let ledgerInfo = yield this._request('get', 'getLedger', { with_fee: true, }); const gasPrice = long.fromValue(ledgerInfo.result.fees.gas_price); const feeLimit = long.fromValue(1000).mul(gasPrice); return { gasPrice, feeLimit, } } catch (err) { throw err; } }; proto._responseData = function (data) { const errorCode = 0; const errorDesc = ''; return { errorCode, errorDesc, result: data, } }; proto._responseError = function (message) { if (!message) { throw new Error('require message'); } const errorCode = message.CODE; return { errorCode, errorDesc: message.MSG, }; }; proto._submitTransaction = function* (data) { try { const res = yield this._request('post', 'submitTransaction', data); const results = res.results; if (Array.isArray(results) && results.length > 0) { const info = results[0]; if (info.error_code === 0) { return this._responseData({ hash: info.hash, }); } return { errorCode: info.error_code, errorDesc: info.error_desc, }; } } catch (err) { throw err; } }; proto._buildOperation = function (type, data) { try { // 使用静态导入的操作类型映射,而不是动态require const operationModule = require('./operation')(type); return operationModule(data); } catch (err) { console.log(err); throw new Error('Operation cannot be resolved'); } }; proto._decodeOperation = function (hexString) { const root = protobuf.Root.fromJSON(require('../crypto/protobuf/bundle.json')); const operation = root.lookupType('protocol.Operation'); const msgBuffer = Buffer.from(hexString, 'hex'); return operation.decode(msgBuffer); }; proto._buildBlob = function (args) { try { let { sourceAddress, gasPrice, feeLimit, nonce, ceilLedgerSeq, operations, metadata } = args; const operationList = []; operations.forEach(item => { const type = item.type; const argsData = item.data; const operationItem = this._buildOperation(type, argsData); operationList.push(operationItem); }); const root = protobuf.Root.fromJSON(require('../crypto/protobuf/bundle.json')); const tx = root.lookupType('protocol.Transaction'); ceilLedgerSeq = ceilLedgerSeq ? long.fromValue(ceilLedgerSeq) : undefined; const payload = { sourceAddress, gasPrice: long.fromValue(gasPrice), feeLimit: long.fromValue(feeLimit), nonce: long.fromValue(nonce), ceilLedgerSeq, operations: operationList, // metadata, }; if (metadata) { payload.metadata = tou8(Buffer.from(metadata)); } const errMsg = tx.verify(payload); if (errMsg) { throw Error(errMsg); } const message = tx.create(payload); const bufferData = tx.encode(message).finish(); return { // transactionBlob: bufferData, transactionBlob: Buffer.from(bufferData).toString('hex'), } } catch (err) { throw err; } }; proto._signBlob = function ({ privateKeys, blob } = args) { try { const buffer = Buffer.from(blob, 'hex'); const uint8ArrayData = tou8(buffer); const signatureArr = []; privateKeys.forEach(privateKey => { signatureArr.push({ signData: signature.sign(uint8ArrayData, privateKey), publicKey: keypair.getEncPublicKey(privateKey), }); }); // return signatureArr; return { signatures: signatureArr, }; } catch (err) { throw err; } }; proto._submit = function* (args) { const { blob, signature } = args; const postData = this._postData(blob, signature); return yield this._submitTransaction(postData); }; proto._isHexString = function (str) { if ((str === '' || !is.string(str))) { return false; } // const hexString = Buffer.from(str, 'hex').toString('hex'); // return (hexString === str); return true; }; proto._isString = function (str) { if (!is.string(str) || str.trim().length === 0 || str.length > 1024) { return false; } return true; }; proto._isTopic = function (str) { if (!is.string(str) || str.trim().length === 0 || str.length > 128) { return false; } return true; }; proto._isSignature = function (arr) { let tag = true; if (!is.array(arr) || arr.length === 0) { tag = false; return tag; } arr.some(item => { if (!is.object(item)) { tag = false; return true; } if (!item.signData || !item.publicKey) { tag = false; return true; } if (!this._isHexString(item.signData)) { tag = false; return true; } if (!keypair.checkEncPublicKey(item.publicKey)) { tag = false; return true; } }); return tag; }; proto._isOperation = function (arr) { let tag = true; if (!is.array(arr) || arr.length === 0) { tag = false; return tag; } arr.some(item => { if (!is.object(item)) { tag = false; return true; } if (!item.type || !item.data) { tag = false; return true; } }); return tag; }; proto._isDatas = function (arr) { let tag = true; if (!is.array(arr) || arr.length === 0) { tag = false; return tag; } arr.some(item => { if (!is.string(item) || item.trim().length === 0 || item.length > 1024) { tag = false; return true; } }); return tag; }; proto._isPrivateKeys = function (arr) { let tag = true; if (!is.array(arr) || arr.length === 0) { tag = false; return tag; } arr.some(item => { if (!keypair.checkEncPrivateKey(item)) { tag = false; return true; } }); return tag; }; /** * * @param obj * @param schema * @returns {boolean} * @private * * eg: schema: { required: false, string: true, address: true, numeric: true, } */ proto._validate = function (obj, schema) { let tag = true; let msg = ''; if (!is.object(obj) || !is.object(schema)) { tag = false; msg = 'INVALID_NUMBER_OF_ARG'; return { tag, msg, }; } Object.keys(schema).some(item => { // required is true if (schema[item].required && is.undefined(obj[item])) { obj[item] = ''; } // numeric is true if (!is.undefined(obj[item]) && schema[item].numeric && !this._verifyValue(obj[item])) { tag = false; switch (item) { case 'amount': msg = 'INVALID_OP_AMOUNT_ERROR'; break; case 'opAmount': msg = 'INVALID_OP_AMOUNT_ERROR'; break; case 'assetAmount': msg = 'INVALID_ASSET_AMOUNT_ERROR'; break; case 'gasPrice': msg = 'INVALID_GASPRICE_ERROR'; break; case 'feeLimit': msg = 'INVALID_FEELIMIT_ERROR'; break; case 'ceilLedgerSeq': msg = 'INVALID_CEILLEDGERSEQ_ERROR'; break; case 'nonce': msg = 'INVALID_NONCE_ERROR'; break; case 'initBalance': msg = 'INVALID_INITBALANCE_ERROR'; break; case 'signtureNumber': msg = 'INVALID_SIGNATURENUMBER_ERROR'; break; case 'totalSupply': msg = 'INVALID_TOKEN_TOTALSUPPLY_ERROR'; break; case 'tokenAmount': msg = 'INVALID_TOKEN_AMOUNT_ERROR'; break; default: msg = 'INVALID_ARGUMENTS'; } return true; } // privateKeys is true if (!is.undefined(obj[item]) && schema[item].privateKeys && !this._isPrivateKeys(obj[item])) { tag = false; msg = `PRIVATEKEY_ONE_ERROR`; return true; } // address is true if (!is.undefined(obj[item]) && schema[item].address && !keypair.checkAddress(obj[item])) { tag = false; switch (item) { case 'sourceAddress': msg = 'INVALID_SOURCEADDRESS_ERROR'; break; case 'destAddress': msg = 'INVALID_DESTADDRESS_ERROR'; break; case 'issuer': msg = 'INVALID_ISSUER_ADDRESS_ERROR'; break; case 'address': msg = 'INVALID_ADDRESS_ERROR'; break; case 'contractAddress': msg = 'INVALID_CONTRACTADDRESS_ERROR'; break; case 'fromAddress': msg = 'INVALID_FROMADDRESS_ERROR'; break; case 'spender': msg = 'INVALID_SPENDER_ERROR'; break; case 'tokenOwner': msg = 'INVALID_TOKENOWNER_ERRPR'; break; default: msg = 'INVALID_ARGUMENTS'; } return true; } // operations is true if (!is.undefined(obj[item]) && schema[item].operations && !this._isOperation(obj[item])) { tag = false; msg = 'INVALID_OPERATIONS'; return true; } // signatures is true if (!is.undefined(obj[item]) && schema[item].signatures && !this._isSignature(obj[item])) { tag = false; msg = 'INVALID_SIGNATURE_ERROR'; return true; } // hex is true if (!is.undefined(obj[item]) && schema[item].hex && !this._isHexString(obj[item])) { tag = false; switch (item) { case 'metadata': msg = 'METADATA_NOT_HEX_STRING_ERROR'; break; case 'blob': msg = 'INVALID_BLOB_ERROR'; break; default: msg = 'METADATA_NOT_HEX_STRING_ERROR'; } return true; } // string is true if (!is.undefined(obj[item]) && schema[item].string && !this._isString(obj[item])) { tag = false; switch (item) { case 'code': msg = 'INVALID_ASSET_CODE_ERROR'; break; case 'issuer': msg = 'INVALID_ISSUER_ADDRESS_ERROR'; break; case 'data': msg = 'INVALID_LOG_DATA_ERROR'; break; case 'metadata': msg = 'INVALID_METADATA_ERROR'; break; case 'payload': msg = 'PAYLOAD_EMPTY_ERROR'; break; case 'input': msg = 'INVALID_INPUT_ERROR'; break; case 'name': msg = 'INVALID_TOKEN_NAME_ERROR'; break; case 'symbol': msg = 'INVALID_TOKEN_SYMBOL_ERROR'; break; case 'key': msg = 'INVALID_DATAKEY_ERROR'; break; default: msg = 'INVALID_ARGUMENTS'; } return true; } // topic is true if (!is.undefined(obj[item]) && schema[item].topic && !this._isTopic(obj[item])) { tag = false; msg = 'INVALID_LOG_TOPIC_ERROR'; return true; } // datas is true if (!is.undefined(obj[item]) && schema[item].datas && !this._isDatas(obj[item])) { tag = false; msg = `INVALID_LOG_DATAS_ERROR`; return true; } // boolean is true if (!is.undefined(obj[item]) && schema[item].boolean && (typeof obj[item] !== 'boolean')) { tag = false; switch (item) { case 'deleteFlag': msg = 'INVALID_DELETEFLAG_ERROR'; break; default: msg = 'INVALID_ARGUMENTS'; } return true; } }); return { tag, msg, }; }; proto._bufToHex = function (buf) { const utf8Str = buf.toString('utf8'); return Buffer.from(utf8Str, 'utf8').toString('hex'); }; proto._bigNumberToString = function (obj, base) { // setup base base = base || 10; // check if obj is type object, not an array and does not have BN properties if (typeof obj === 'object' && obj !== null && !Array.isArray(obj) && !('lessThan' in obj)) { // move through plain object Object.keys(obj).forEach(function (key) { // recurively converty item obj[key] = proto._bigNumberToString(obj[key], base); }) } // obj is an array if (Array.isArray(obj)) { // convert items in array obj = obj.map(function (item) { // convert item to a string if bignumber return proto._bigNumberToString(item, base); }) } // if obj is number, convert to string if (typeof obj === 'number') return obj + ''; // if not an object bypass if (typeof obj !== 'object' || obj === null) return obj; // if the object to does not have BigNumber properties, bypass if (!('toString' in obj) || !('lessThan' in obj)) return obj; // if object has bignumber properties, convert to string with base return obj.toString(base); }; proto._longToInt = function (obj) { // check if obj is type object, not an array and does not have long properties if (typeof obj === 'object' && obj !== null && !Array.isArray(obj) && !('low' in obj)) { // move through plain object Object.keys(obj).forEach(function (key) { // recurively converty item obj[key] = proto._longToInt(obj[key]); }) } // obj is an array if (Array.isArray(obj)) { // convert items in array obj = obj.map(function (item) { // convert item to an int if long return proto._longToInt(item); }) } // if not an object bypass if (typeof obj !== 'object' || obj === null) return obj; // if the object to does not have long properties, bypass if (!('low' in obj)) return obj; // if object has long properties, convert to int return long.fromValue(obj).toNumber(); }; proto._isContractAddress = function* (address) { const data = yield this._request('get', 'getAccount', { address, }); if (data.error_code !== 0) { return this._responseError(errors.ACCOUNT_NOT_EXIST); } const result = data.result; if (result.contract) { return true; } return false; }; proto._isAvailableToken = function* (contractAddress) { if (!keypair.checkAddress(contractAddress)) { return this._responseError(errors.INVALID_CONTRACTADDRESS_ERROR); } const isContractAddress = yield this._isContractAddress(contractAddress); if (!isContractAddress) { return this._responseError(errors.CONTRACTADDRESS_NOT_CONTRACTACCOUNT_ERROR); } let data = yield this._request('get', 'getAccount', { address: contractAddress, }); if (data.error_code !== 0) { return this._responseData({ isValid: false, }); } data = data.result; const contract = data.contract.metadatas; const metadatas = data.metadatas; let key = ''; let value = ''; if (metadatas && is.array(metadatas)) { metadatas.some(item => { if (item.key === 'global_attribute') { key = 'global_attribute'; value = item.value; return true; } }); if (key !== 'global_attribute') { return this._responseData({ isValid: false, }); } const info = JSON.parse(value); if ('1.0' !== info.ctp) { return this._responseData({ isValid: false, }); } if (!info.symbol || info.symbol < 0 || info.symbol > 8) { return this._responseData({ isValid: false, }); } const schema = { balance: { required: true, numeric: true, }, name: { required: true, string: true, }, symbol: { required: true, string: true, }, totalSupply: { required: true, numeric: true, }, contractOwner: { required: true, address: true, }, }; if (!this._validate(info, schema).tag) { return this._responseData({ isValid: false, }); } return this._responseData({ isValid: true, }); } else { return this._responseData({ isValid: false, }); } }; proto._isAvailableVersion = function (str) { const reg = /^\d+(\.\d+)?$/; return ( is.string(str) && reg.test(str) && long.fromValue(str).greaterThanOrEqual(0) && long.fromValue(str).lessThanOrEqual(long.MAX_VALUE) ); }; proto._isAvailableOP = function (str) { const reg = /^(([1-9]\d*)+|0)(\.\d{1,8})?$/; return ( is.string(str) && reg.test(str) && long.fromValue(str).greaterThanOrEqual(0) && long.fromValue(str).lessThanOrEqual(long.MAX_VALUE.divide(Math.pow(10, 8))) ); } proto._handleRequestError = function (error) { if (error.response) { const { status, data } = error.response; return new Error(`请求失败: 状态码 ${status}, 响应数据: ${JSON.stringify(data)}`); } return new Error(`网络连接失败: ${error.message}`); };