xek-sdk
Version:
SDK for katana blockchain
378 lines (331 loc) • 12.1 kB
JavaScript
'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;