"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const es6_promisify_1 = require("es6-promisify");
const abi = require("ethereumjs-abi");
const TruffleContract = require("truffle-contract");
const web3_1 = require("web3");
const gasLimits_js_1 = require("../gasLimits.js");
const commonTypes_1 = require("./commonTypes");
const configService_1 = require("./configService");
const loggingService_1 = require("./loggingService");
// haven't figured out how to get web3 typings to properly expose the Web3 constructor.
// v1.0 may improve on this entire Web3 typings experience
/* tslint:disable-next-line:no-var-requires */
const webConstructor = require("web3");
class Utils {
    static get NULL_ADDRESS() { return "0x0000000000000000000000000000000000000000"; }
    static get NULL_HASH() { return "0x0000000000000000000000000000000000000000000000000000000000000000"; }
    /**
     * Returns TruffleContract given the name of the contract (like "SchemeRegistrar").
     * Optimized for synchronicity issues encountered with MetaMask.
     * Throws an exception if it can't load the contract.
     * Uses the asynchronous web.eth.getAccounts to obtain the default account (good with MetaMask).
     * @param contractName like "SchemeRegistrar"
     */
    static requireContract(contractName) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                const artifact = require(`../migrated_contracts/${contractName}.json`);
                const contract = new TruffleContract(artifact);
                const myWeb3 = yield Utils.getWeb3();
                contract.setProvider(myWeb3.currentProvider);
                contract.defaults({
                    from: yield Utils.getDefaultAccount(),
                    gas: gasLimits_js_1.gasLimitsConfig.gasLimit_runtime,
                });
                loggingService_1.LoggingService.debug(`requireContract: loaded ${contractName}`);
                return contract;
            }
            catch (ex) {
                loggingService_1.LoggingService.error(`requireContract failing: ${ex}`);
                throw Error(`requireContract: unable to load solidity contract: ${contractName}: ${ex}`);
            }
        });
    }
    /**
     * Returns the web3 object.
     * When called for the first time, web3 is initialized from the Arc.js configuration.
     * Throws an exception when web3 cannot be initialized.
     */
    static getWeb3() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (Utils.web3) {
                return Utils.web3;
            }
            loggingService_1.LoggingService.debug("Utils.getWeb3: getting web3");
            let preWeb3;
            let globalWeb3;
            if ((typeof global !== "undefined") && global.web3) {
                loggingService_1.LoggingService.debug("Utils.getWeb3: found web3 in global");
                globalWeb3 = global.web3;
            }
            else if ((typeof window !== "undefined") && window.web3) {
                loggingService_1.LoggingService.debug("Utils.getWeb3: found web3 in window");
                globalWeb3 = window.web3;
            }
            if (typeof globalWeb3 !== "undefined") {
                loggingService_1.LoggingService.debug("Utils.getWeb3: instantiating web3 with currentProvider");
                // Look for injected web3 e.g. by truffle in migrations, or MetaMask in the browser window
                // Instead of using the injected Web3.js directly best practice is to use the version of web3.js we have bundled
                /* tslint:disable-next-line:max-line-length */
                // see https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#partly_sunny-web3---ethereum-browser-environment-check
                preWeb3 = new webConstructor(globalWeb3.currentProvider);
            }
            else if (Utils.alreadyTriedAndFailed) {
                // then avoid time-consuming and futile retry
                throw new Error("Utils.getWeb3: already tried and failed");
            }
            else {
                /* tslint:disable-next-line:max-line-length */
                loggingService_1.LoggingService.debug(`Utils.getWeb3: instantiating web3 with configured provider ${configService_1.ConfigService.get("providerUrl")}:${configService_1.ConfigService.get("providerPort")}`);
                // No web3 is injected, look for a provider at providerUrl:providerPort (which defaults to localhost)
                // This happens when running tests, or in a browser that is not running MetaMask
                preWeb3 = new webConstructor(new web3_1.providers.HttpProvider(`${configService_1.ConfigService.get("providerUrl")}:${configService_1.ConfigService.get("providerPort")}`));
            }
            const connected = yield es6_promisify_1.promisify(preWeb3.net.getListening)()
                .then((isListening) => {
                return isListening;
            })
                .catch((error) => {
                return false;
            });
            if (!connected) {
                Utils.alreadyTriedAndFailed = true;
                throw new Error("Utils.getWeb3: web3 is not connected to a net");
            }
            if (typeof window !== "undefined") {
                // Add to window for easy use in the console
                window.web3 = preWeb3;
            }
            return (Utils.web3 = preWeb3);
        });
    }
    /**
     * Returns a value from the given transaction log.
     * Undefined if not found for any reason.
     *
     * @param tx The transaction
     * @param arg The name of the property whose value we wish to return from the args object:
     *  tx.logs[index].args[argName]
     * @param eventName Overrides index, identifies which log,
     *  where tx.logs[n].event === eventName
     * @param index Identifies which log when eventName is not given
     */
    static getValueFromLogs(tx, arg, eventName = null, index = 0) {
        /**
         *
         * tx is an object with the following values:
         *
         * tx.tx      => transaction hash, string
         * tx.logs    => array of decoded events that were triggered within this transaction
         * tx.receipt => transaction receipt object, which includes gas used
         *
         * tx.logs look like this:
         *
         * [ { logIndex: 13,
         *     transactionIndex: 0,
         *     transactionHash: "0x999e51b4124371412924d73b60a0ae1008462eb367db45f8452b134e5a8d56c8",
         *     blockHash: "0xe35f7c374475a6933a500f48d4dfe5dce5b3072ad316f64fbf830728c6fe6fc9",
         *     blockNumber: 294,
         *     address: "0xd6a2a42b97ba20ee8655a80a842c2a723d7d488d",
         *     type: "mined",
         *     event: "NewOrg",
         *     args: { _avatar: "0xcc05f0cde8c3e4b6c41c9b963031829496107bbb" } } ]
         */
        if (!tx.logs || !tx.logs.length) {
            // TODO: log "getValueFromLogs: Transaction has no logs");
            return undefined;
        }
        if (eventName && (eventName.length)) {
            for (let i = 0; i < tx.logs.length; i++) {
                if (tx.logs[i].event === eventName) {
                    index = i;
                    break;
                }
            }
            if (typeof index === "undefined") {
                // TODO: log  `getValueFromLogs: There is no event logged with eventName ${eventName}`
                return undefined;
            }
        }
        else if (typeof index === "undefined") {
            index = tx.logs.length - 1;
        }
        if (tx.logs[index].type !== "mined") {
            // TODO: log  `getValueFromLogs: transaction has not been mined: ${tx.logs[index].event}`
            return undefined;
        }
        const result = tx.logs[index].args[arg];
        if (!result) {
            // TODO: log  `getValueFromLogs: This log does not seem to have a field "${arg}": ${tx.logs[index].args}`
            return undefined;
        }
        return result;
    }
    /**
     * Returns the address of the default user account.
     *
     * Has the side-effect of setting web3.eth.defaultAccount.
     *
     * Throws an exception on failure.
     */
    static getDefaultAccount() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const localWeb3 = yield Utils.getWeb3();
            return es6_promisify_1.promisify(localWeb3.eth.getAccounts)().then((accounts) => {
                const defaultAccount = localWeb3.eth.defaultAccount = accounts[0];
                if (!defaultAccount) {
                    throw new Error("accounts[0] is not set");
                }
                return defaultAccount;
            });
        });
    }
    /**
     * Return the hash of a string the same way solidity would, and to a format that will be
     * properly translated into a bytes32 that solidity expects
     * @param str a string
     */
    static SHA3(str) {
        return Utils.keccak256(["string"], [str]);
    }
    /**
     * Return the tightly-packed hash of any arbitrary array of
     * objects just as Solidity's `keccak256` function would do.
     *
     * Items in the `types` array must appear in the same order in which the values would be
     * passed to Solidity's `keccak256` function.
     *
     * Type names can be:
     *   "bytes[N]' - fails if (N < 1 || N > 32)
     *   "string'
     *   "bool'
     *   "address'
     *   "uint[N]'  - fails if ((N % 8) || (N < 8) || (N > 256))
     *   "int[N]'   - fails if ((N % 8) || (N < 8) || (N > 256))
     *
     * Use "bytes32" for a Hash value
     *
     * See: https://github.com/ethereumjs/ethereumjs-abi
     *
     * @param types array of type names.
     * @param values - the values to pack and hash.  These must appear in the same order in which the types are ordered.
     */
    static keccak256(types, values) {
        return `0x${abi.soliditySHA3(types, values).toString("hex")}`;
    }
    /**
     * Convert scheme permissions string to a number
     * @param {string} permissions
     */
    static permissionsStringToNumber(permissions) {
        if (!permissions) {
            return 0;
        }
        return Number(permissions);
    }
    /**
     * Convert number to a scheme permissions string
     * @param {Number} permissions
     */
    static numberToPermissionsString(permissions) {
        if (!permissions) {
            permissions = commonTypes_1.SchemePermissions.None;
        }
        return `0x${("00000000" + permissions.toString(16)).substr(-8)}`;
    }
}
Utils.web3 = undefined;
Utils.alreadyTriedAndFailed = false;
exports.Utils = Utils;
var web3_2 = require("web3");
exports.Web3 = web3_2.Web3;
//# sourceMappingURL=utils.js.map