"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 Contract = 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 Truffle contract wrapper 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 Contract(artifact);
                const myWeb3 = yield Utils.getWeb3();
                contract.setProvider(myWeb3.currentProvider);
                contract.setNetwork(yield Utils.getNetworkId());
                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 {
                let url = configService_1.ConfigService.get("providerUrl");
                const port = configService_1.ConfigService.get("providerPort");
                if (port) {
                    url = `${url}:${port}`;
                }
                /* tslint:disable-next-line:max-line-length */
                loggingService_1.LoggingService.debug(`Utils.getWeb3: instantiating web3 with configured provider at ${url}`);
                // 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(url));
            }
            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;
            }
            Utils.networkId = (yield es6_promisify_1.promisify(preWeb3.version.getNetwork)());
            return (Utils.web3 = preWeb3);
        });
    }
    /**
     * 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 current token balance for the given token and agent.
     */
    static getTokenBalance(agentAddress, tokenAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!tokenAddress) {
                throw new Error("Utils.getTokenBalance: tokenAddress is not defined");
            }
            if (!agentAddress) {
                throw new Error("Utils.getTokenBalance: agentAddress is not defined");
            }
            const token = yield (yield Utils.requireContract("BasicToken")).at(tokenAddress);
            return token.balanceOf(agentAddress);
        });
    }
    /**
     * Return the current ETH balance for the given agent.
     */
    static getEthBalance(agentAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!agentAddress) {
                throw new Error("Utils.getEthBalance: agentAddress is not defined");
            }
            const web3 = yield Utils.getWeb3();
            return es6_promisify_1.promisify((callback) => web3.eth.getBalance(agentAddress, web3.eth.defaultBlock, callback))()
                .then((balance) => {
                return balance;
            });
        });
    }
    /**
     * 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 SchemePermissions | DefaultSchemePermissions 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)}`;
    }
    /**
     * Returns promise of the name of the current or given network
     * @param id Optional id of the network
     */
    static getNetworkName(id) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!id) {
                id = yield Utils.getNetworkId();
            }
            switch (id) {
                case "1":
                    return "Live";
                case "2":
                    return "Morden";
                case "3":
                    return "Ropsten";
                case "4":
                    return "Rinkeby";
                case "42":
                    return "Kovan";
                // the id that arc.js hardwires for ganache
                case "1512051714758":
                    return "Ganache";
                default:
                    return "Unknown";
            }
        });
    }
    /**
     * Returns promise of the id of the current network
     */
    static getNetworkId() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!Utils.networkId) {
                yield Utils.getWeb3();
            }
            return Utils.networkId;
        });
    }
    /**
     * Returns promise of the address of the global GEN token.
     */
    static getGenTokenAddress() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const networkName = (yield Utils.getNetworkName()).toLowerCase();
            const addresses = configService_1.ConfigService.get("globalGenTokenAddresses");
            const address = addresses[networkName];
            return address ? address : addresses.default;
        });
    }
    /**
     * Returns a promise of the given account's GEN token balance.
     * @param agentAddress
     */
    static getGenTokenBalance(agentAddress) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const genTokenAddress = yield Utils.getGenTokenAddress();
            return Utils.getTokenBalance(agentAddress, genTokenAddress);
        });
    }
    /**
     * Returns the truffle artifact json for the given contract
     * @param contractName
     */
    static getTruffleArtifactForContract(contractName) {
        return require(`../migrated_contracts/${contractName}.json`);
    }
}
Utils.web3 = undefined;
Utils.alreadyTriedAndFailed = false;
exports.Utils = Utils;
var web3_2 = require("web3");
exports.Web3 = web3_2.Web3;
//# sourceMappingURL=utils.js.map