UNPKG

xek-sdk

Version:

SDK for katana blockchain

378 lines (331 loc) 12.1 kB
'use strict'; const API = require('./api'); const utils = require('./utils/utils'); const Wallet = require('./wallet'); const {CURVE_TYPE, GAS_LIMIT, FEE, FROM, TX_FEE} = require('./constant'); const Web3 = require('web3'); const web3 = new Web3(); const BigNumber = require ('bignumber.js'); class Contract { /** * Create a new instance of Contract * * @param {String} chain Chain ID * @param {String} api Wallet api URL * @param {Object} abi JSON abi of contract * @param {String} byteCode Byte code of contract */ constructor(chain, api, abi, byteCode) { if (!api) { this.webAPI = new API(); } else { this.webAPI = new API(api); } this.wallet = Wallet; this.utils = utils; this.CHAINID = chain; this.abi = abi; this.byteCode = byteCode; // events let eventIds = {}; if (abi) { let events = abi.filter(event => event.type === 'event'); events.forEach(element => { let arrInput=[]; element.inputs.forEach(data=> { arrInput = [...arrInput, data.type]; }); const string = element.name+'('+arrInput.join(',')+')'; const id = web3.eth.abi.encodeEventSignature(string).toUpperCase().slice(2); const event = { name: element.name, inputs: element.inputs } eventIds[id] = event; }); } this.events = eventIds; // functions let funIds = []; if(abi) { const funcs = abi.filter(func => func.type === 'function'); funcs.forEach(element => { let funcName = element.name + '('; let arrOutput=[]; let arrInput=[]; element.inputs.forEach(data=> { arrInput = [...arrInput, data.type]; funcName += data.type + ','; }); element.outputs.forEach(data=> { arrOutput = [...arrOutput, data.type]; }); funcName = funcName.slice(0, -1) + ")"; const id = web3.eth.abi.encodeFunctionSignature(funcName); const fn = { id: id, name: element.name, outputs: arrOutput, inputs: arrInput } funIds.push(fn); }); } this.funcs = funIds; } /** * Deploy new contract * * @param {String} from Address of deployer * @param {String} privateKey Private key of deployer * @param {Array} args List argument of contract constructor */ async deploy(from, privateKey, args) { try { //verify inputs if (!this.utils.isAddress (from)) { return null; } //get balance of sender let balance = await this.webAPI.getBalance (from); let fee = new BigNumber (TX_FEE); //check balance sender if (!( new BigNumber (balance).isGreaterThanOrEqualTo (fee) )) { return null; } //get sequence let sequence = await this.webAPI.getSequence(from); sequence = Number(sequence); const myContract = new web3.eth.Contract(this.abi); let data = myContract.deploy({ data: this.byteCode, arguments: args }).encodeABI(); // create tx data = data.toUpperCase(); sequence = new BigNumber (sequence).plus (1).toNumber(); let tx = await Wallet.createTransactionDeploy(from, data, sequence, this.CHAINID); // sign tx const sig = await this.wallet.signatureOffline(tx, privateKey); // broadcast tx const publicKey = await this.wallet.getPublicKeyOffline(privateKey, CURVE_TYPE); //create envelop for transaction const txEnvelope = this.wallet.createEnvelopTx (from, publicKey, sig, tx); let res = await this.webAPI.sendTransaction(txEnvelope); if (res.success && !res.res.Exception) { let contractAddress = res && res.res && res.res.Receipt && res.res.Receipt.ContractAddress; let buffer = Buffer.from(contractAddress); buffer = buffer ? buffer.toString("hex").toUpperCase() : ""; return { success: true, data: buffer }; } else { res.success =false; return res; }; } catch (error) { return null; } } /** * Call a constant method of contract * * @param {*} typeOutput Type of output * @param {*} from Address of sender * @param {*} contractAddr Contract address * @param {*} data Data */ async callPublicFunc(typeOutput, contractAddr, data) { try { const tx = { Input: { Address: FROM, Amount: 0, }, Address: contractAddr, Data: data, GasLimit: GAS_LIMIT, // thuannd start Fee: TX_FEE // thuannd end }; const res = await this.webAPI.callTransaction(tx); if (res.success) { const result = res && res.data && res.data.Result && res.data.Result.Return; const buffer = Buffer.from(result); let decoded = web3.eth.abi.decodeParameters(typeOutput, buffer.toString('hex')); if(typeOutput.length === 1) { return decoded[0]; } return decoded; } return null; } catch (error) { return null; } } /** * Call a contract method * * @param {String} method Method * @param {*} typeOutput Type of output * @param {*} from Address of sender * @param {*} contractAddr Contract address * @param {*} data Data * @param {*} privateKey Private key of sender */ async callPostFunc(typeOutput, from, contractAddr, data, privateKey) { try { //verify inputs if (!this.utils.isAddress (from)) { return null; } if (!this.utils.isAddress (contractAddr)) { return null; } //get balance of sender let balance = await this.webAPI.getBalance (from); let fee = new BigNumber (TX_FEE); //check balance sender if (!( new BigNumber (balance).isGreaterThanOrEqualTo (fee) )) { return null; } // get sequence let sequence = await this.webAPI.getSequence(from); sequence = new BigNumber (sequence).plus (1).toNumber(); // create tx let tx = await this.wallet.createTransactionTransact(from, contractAddr, data, sequence, this.CHAINID); // sign tx let sig = await this.wallet.signatureOffline(tx, privateKey); // broadcast tx let publicKey = await this.wallet.getPublicKeyOffline(privateKey, CURVE_TYPE); let txEnvelope = this.wallet.createEnvelopTx (from, publicKey, sig, tx); let res = await this.webAPI.sendTransaction(txEnvelope); if(!res.success || res.res.Exception) { res.success = false; return res; } let result = res && res.res.Result && res.res.Result.Return; if (!result || (result && Buffer.from(result).toString('hex') === '')) { return res; } let dataResult = web3.eth.abi.decodeParameters(typeOutput, Buffer.from(result).toString('hex')); res.dataResult = dataResult; return res; } catch (error) { return null; } } /** * Broadcast transaction call contract * * @param {String} method Method of smart contract * @param {String} from Address of sender * @param {String} contractAddr Contract address * @param {String} privateKey Private key of sender * @param {String} data Data */ broadcastBurrowTx(method, from, contractAddr, privateKey, data) { const constantMethod = this.getConstantMethod(method); if (constantMethod) { const {constant, typeOutput} = constantMethod; data = data.slice(2).toUpperCase(); if (constant !== null) { if (constant) { return this.callPublicFunc(typeOutput, contractAddr, data); } else { return this.callPostFunc(typeOutput, from, contractAddr, data, privateKey); } } } } /** * Return encode abi of method * @param {*} method Method * @param {*} arrayInput Json ABI of method */ getMethod(method, arrayInput) { try { let myContract = new web3.eth.Contract(this.abi); return myContract.methods[method](...arrayInput).encodeABI(); } catch (error) { return null; } } /** * Decode events * * @param {Object} events Events */ getEvents(events) { let result = []; events.forEach(event => { const log = event.Log; if(log != null) { let topics = log.Topics; const address = log.Address; const data = log.Data; topics = topics.map(topic => { return Buffer.from(topic).toString('hex').toUpperCase(); }); let event = this.events[topics[0]]; if(event !== undefined) { let inputs = event.inputs; let dt = web3.eth.abi.decodeLog(inputs, Buffer.from(data).toString('hex'), topics.slice(1)); result.push({ name: event.name, data: dt, contract: Buffer.from(address).toString('hex').toUpperCase() }) } } }); return result; } /** * Get constant method. * @param {String} keyMethod Method name */ getConstantMethod(keyMethod) { if (this.abi) { const itemMethod = this.abi.find(method => method.name === keyMethod); if (itemMethod) { const constant = itemMethod.constant; let outputs = []; itemMethod.outputs.forEach(output => { outputs = [...outputs, output.type]; }); return { typeOutput: outputs, constant }; } } return null; }; /** * Decode data to name and params. * * @param {String} data hex string */ decodeData(data) { this.funcs.forEach(func => { if(data.indexOf(func.id) === 0) { const paramData = '0x' + data.slice(func.id.length); const param = web3.eth.abi.decodeParameters(func.inputs, paramData); let params = []; const len = func.inputs.length; for(let i = 0; i < len; i++) { params = [...params, param[i]]; } return { name: func.name, params: params } } }); return null; } } module.exports = Contract;