jcc-moac-abi
Version:
Decoder and encoder for the MOAC ABI and decode events from MOAC transactions
177 lines • 5.86 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MoacABI = void 0;
const ethers_1 = require("ethers");
const isFunction = require("lodash/isFunction");
// Global ABI registry (replaces abi-decoder)
const _abis = [];
let _iface = null;
function _getIface() {
if (!_iface) {
_iface = new ethers_1.Interface(_abis);
}
return _iface;
}
function _convertValue(value, type) {
if (typeof value === "bigint") {
return value.toString();
}
if (type === "address") {
return String(value).toLowerCase();
}
return value;
}
// Topics in MOAC logs may not always be zero-padded to 32 bytes.
// Ethers requires 32-byte hex topics, so we pad them before decoding.
function _padTopic(topic) {
return topic.length < 66 ? "0x" + topic.slice(2).padStart(64, "0") : topic;
}
/**
* decoder and encoder for moac
*
* @export
* @class MoacABI
*/
class MoacABI {
/**
* Creates an instance of MoacABI.
* @param {Contract} contract moac contract instance
* @memberof MoacABI
*/
constructor(contract) {
/**
* get item of function meta data
*
* @param {string} name defined function name in the abi
* @param {*} args parameters according to the defined inputs
* @returns {IABIItem}
* @memberof MoacABI
*/
this.getAbiItem = (name, ...args) => {
const method = this._contract[name];
if (!isFunction(method)) {
throw new Error(`The contract doesn't contain "${name}" function`);
}
const filterABIs = this._abi.filter((item) => item.name === name);
let abi;
if (filterABIs.length === 1) {
abi = filterABIs[0];
}
else {
abi = filterABIs.find((item) => item.inputs.length === args.length);
if (!abi) {
throw new Error("Invalid number of arguments to Solidity function");
}
}
return abi;
};
/**
* encode the input value by function name
*
* @param {string} name defined function name in the abi
* @param {*} args parameters according to the defined inputs
* @returns {string}
* @memberof MoacABI
*/
this.encode = (name, ...args) => {
const method = this._contract[name];
if (!isFunction(method)) {
throw new Error(`The contract doesn't contain "${name}" function`);
}
const filterABIs = this._abi.filter((item) => item.name === name);
let encodedData;
if (filterABIs.length === 1) {
encodedData = method["getData"].apply(null, args);
}
else {
const abi = filterABIs.find((item) => item.inputs.length === args.length);
if (!abi) {
throw new Error("Invalid number of arguments to Solidity function");
}
// detail: https://github.com/MOACChain/chain3/blob/master/lib/chain3/function.js#L282
const typename = abi.inputs.map((input) => input.type).join(",");
encodedData = method[typename].getData.apply(null, args);
}
if (encodedData.includes("NaN")) {
throw new Error('The encoded data contains "NaN", please check the input arguments');
}
return encodedData;
};
if (contract && Array.isArray(contract.abi)) {
this._contract = contract;
this._abi = contract.abi;
}
else {
throw new Error("The input value isn't a contract instance");
}
}
/**
* decode the input value
*
* @static
* @param {string} data
* @returns {IDecoded[]}
* @memberof MoacABI
*/
static decode(data) {
const parsed = _getIface().parseTransaction({ data });
return {
name: parsed.name,
params: parsed.fragment.inputs.map((input, i) => ({
name: input.name,
value: _convertValue(parsed.args[i], input.type),
type: input.type,
})),
};
}
/**
* decode moac transaction logs
*
* @static
* @param {ILog[]} logs
* @returns {IDecodedLog[]} if event is defined and decode succeed, return log that contains
* events as input arguments and name as event's name, otherwise return itself.
* @memberof MoacABI
*/
static decodeLogs(logs) {
const iface = _getIface();
return logs.filter((log) => log.topics.length > 0).map((logItem) => {
const paddedTopics = logItem.topics.map(_padTopic);
const parsed = iface.parseLog({ topics: paddedTopics, data: logItem.TxData });
if (parsed) {
const decodedParams = parsed.fragment.inputs.map((input, i) => ({
name: input.name,
type: input.type,
value: _convertValue(parsed.args[i], input.type),
}));
return Object.assign(logItem, { events: decodedParams, name: parsed.name });
}
return logItem;
});
}
/**
* add abi to registry
*
* @static
* @param {IABIItem[]} abi
* @memberof MoacABI
*/
static addABI(abi) {
_abis.push(...abi);
_iface = null;
}
/**
* remove all ABIs from registry
*
* @static
* @param {IABIItem[]} abi
* @memberof MoacABI
*/
static removeABI(_abi) {
_abis.length = 0;
_iface = null;
}
}
exports.default = MoacABI;
exports.MoacABI = MoacABI;
//# sourceMappingURL=abi.js.map