vue-tronlink
Version:
Vue support for the TronLink browser extension
249 lines (191 loc) • 7.59 kB
JavaScript
import TronWeb from 'tronweb';
import utils from '../../utils';
import Method from './method';
import injectpromise from 'injectpromise';
export default class Contract {
constructor(tronWeb = false, abi = [], address = false) {
if (!tronWeb || !tronWeb instanceof TronWeb)
throw new Error('Expected instance of TronWeb');
this.tronWeb = tronWeb;
this.injectPromise = injectpromise(this);
this.address = address;
this.abi = abi;
this.eventListener = false;
this.bytecode = false;
this.deployed = false;
this.lastBlock = false;
this.methods = {};
this.methodInstances = {};
this.props = [];
if (this.tronWeb.isAddress(address))
this.deployed = true;
else
this.address = false;
this.loadAbi(abi);
}
async _getEvents(options = {}) {
const events = await this.tronWeb.event.getEventsByContractAddress(this.address, options);
const [latestEvent] = events.sort((a, b) => b.block - a.block);
const newEvents = events.filter((event, index) => {
if (options.resourceNode && event.resourceNode &&
options.resourceNode.toLowerCase() !== event.resourceNode.toLowerCase()) {
return false
}
const duplicate = events.slice(0, index).some(priorEvent => (
JSON.stringify(priorEvent) == JSON.stringify(event)
));
if (duplicate)
return false;
if (!this.lastBlock)
return true;
return event.block > this.lastBlock;
});
if (latestEvent)
this.lastBlock = latestEvent.block;
return newEvents;
}
async _startEventListener(options = {}, callback) {
if (utils.isFunction(options)) {
callback = options;
options = {};
}
if (this.eventListener)
clearInterval(this.eventListener);
if (!this.tronWeb.eventServer)
throw new Error('Event server is not configured');
if (!this.address)
throw new Error('Contract is not configured with an address');
this.eventCallback = callback;
await this._getEvents(options);
this.eventListener = setInterval(() => {
this._getEvents(options).then(newEvents => newEvents.forEach(event => {
this.eventCallback && this.eventCallback(event)
})).catch(err => {
console.error('Failed to get event list', err);
});
}, 3000);
}
_stopEventListener() {
if (!this.eventListener)
return;
clearInterval(this.eventListener);
this.eventListener = false;
this.eventCallback = false;
}
hasProperty(property) {
return this.hasOwnProperty(property) || this.__proto__.hasOwnProperty(property);
}
loadAbi(abi) {
this.abi = abi;
this.methods = {};
this.props.forEach(prop => delete this[prop]);
abi.forEach(func => {
// Don't build a method for constructor function. That's handled through contract create.
if (!func.type || /constructor/i.test(func.type))
return;
const method = new Method(this, func);
const methodCall = method.onMethod.bind(method);
const {
name,
functionSelector,
signature
} = method;
this.methods[name] = methodCall;
this.methods[functionSelector] = methodCall;
this.methods[signature] = methodCall;
this.methodInstances[name] = method;
this.methodInstances[functionSelector] = method;
this.methodInstances[signature] = method;
if (!this.hasProperty(name)) {
this[name] = methodCall;
this.props.push(name);
}
if (!this.hasProperty(functionSelector)) {
this[functionSelector] = methodCall;
this.props.push(functionSelector);
}
if (!this.hasProperty(signature)) {
this[signature] = methodCall;
this.props.push(signature);
}
});
}
decodeInput(data) {
const methodName = data.substring(0, 8);
const inputData = data.substring(8);
if (!this.methodInstances[methodName])
throw new Error('Contract method ' + methodName + " not found");
const methodInstance = this.methodInstances[methodName];
return {
name: methodInstance.name,
params: this.methodInstances[methodName].decodeInput(inputData),
}
}
async new(options, privateKey = this.tronWeb.defaultPrivateKey, callback = false) {
if (utils.isFunction(privateKey)) {
callback = privateKey;
privateKey = this.tronWeb.defaultPrivateKey;
}
if (!callback)
return this.injectPromise(this.new, options, privateKey);
try {
const address = this.tronWeb.address.fromPrivateKey(privateKey);
const transaction = await this.tronWeb.transactionBuilder.createSmartContract(options, address);
const signedTransaction = await this.tronWeb.trx.sign(transaction, privateKey);
const contract = await this.tronWeb.trx.sendRawTransaction(signedTransaction);
if (contract.code)
return callback({
error: contract.code,
message: this.tronWeb.toUtf8(contract.message)
})
await utils.sleep(3000);
return this.at(signedTransaction.contract_address, callback);
} catch (ex) {
return callback(ex);
}
}
async at(contractAddress, callback = false) {
if (!callback)
return this.injectPromise(this.at, contractAddress);
try {
const contract = await this.tronWeb.trx.getContract(contractAddress);
if (!contract.contract_address)
return callback('Unknown error: ' + JSON.stringify(contract, null, 2));
this.address = contract.contract_address;
this.bytecode = contract.bytecode;
this.deployed = true;
this.loadAbi(contract.abi ? contract.abi.entrys : []);
return callback(null, this);
} catch (ex) {
if (ex.toString().includes('does not exist'))
return callback('Contract has not been deployed on the network');
return callback(ex);
}
}
events(options = {}, callback = false) {
if (utils.isFunction(options)) {
callback = options;
options = {};
}
if (!utils.isFunction(callback))
throw new Error('Callback function expected');
const self = this;
return {
start(startCallback = false) {
if (!startCallback) {
self._startEventListener(options, callback);
return this;
}
self._startEventListener(options, callback).then(() => {
startCallback();
}).catch(err => {
startCallback(err)
});
return this;
},
stop() {
self._stopEventListener();
}
};
}
}