"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.initProtocol = exports.sanitizeAndInit = exports.optionDefinitions = void 0;
const fs = __importStar(require("fs"));
const dotenv_1 = require("dotenv");
const ethers_1 = require("ethers");
const zk = __importStar(require("zksync-web3"));
const command_line_args_1 = __importDefault(require("command-line-args"));
const nxtp_utils_1 = require("@connext/nxtp-utils");
const domain_1 = require("../../domain");
const config_1 = require("../../config");
const helpers_1 = require("./helpers");
const assets_1 = require("./helpers/assets");
const messaging_1 = require("./helpers/messaging");
const config_2 = require("./config");
(0, dotenv_1.config)();
exports.optionDefinitions = [
    { name: "name", defaultOption: true },
    { name: "network", type: String },
    { name: "env", type: String },
    { name: "domains", type: String, multiple: true },
];
/**
 * Make sure config is set up properly and start initializtion.
 */
const sanitizeAndInit = async () => {
    let cmdArgs;
    try {
        cmdArgs = (0, command_line_args_1.default)(exports.optionDefinitions);
    }
    catch (err) {
        throw new Error(`Parsing arguments failed, cmdArgs: ${process.argv}`);
    }
    // Validate command line arguments
    const { network, env, domains: _domains } = cmdArgs;
    if (!["staging", "production"].includes(env)) {
        throw new Error(`Environment should be either staging or production, env: ${env}`);
    }
    if (!["testnet", "mainnet"].includes(network)) {
        throw new Error(`Network should be either testnet or mainnet, network: ${network}`);
    }
    const useStaging = env === "staging";
    console.log(`USING ${useStaging ? "STAGING" : "PRODUCTION"} AS ENVIRONMENT`);
    // Read init.json if exists
    const path = process.env.INIT_CONFIG_FILE ?? "init.json";
    let overrideConfig;
    if (fs.existsSync(path)) {
        const json = fs.readFileSync(path, { encoding: "utf-8" });
        overrideConfig = JSON.parse(json);
    }
    // Validate init config schema
    const initConfig = overrideConfig ?? config_2.DEFAULT_INIT_CONFIG[network][env];
    console.log(initConfig);
    const validate = nxtp_utils_1.ajv.compile(helpers_1.InitConfigSchema);
    const valid = validate(initConfig);
    if (!valid) {
        throw new Error(validate.errors?.map((err) => JSON.stringify(err, null, 2)).join(","));
    }
    const supported = initConfig.supportedDomains;
    const domains = _domains ?? supported;
    for (const domain of domains) {
        if (!supported.includes(domain)) {
            throw new Error(`Unsupported domain parsed!, domain: ${domain}, supported: ${supported}`);
        }
    }
    // Sanitation checks for hub domain and assets configuration
    const hubDomain = initConfig.hub;
    if (!supported.includes(hubDomain)) {
        throw new Error(`Supported domains MUST include the hub domain. hub: ${hubDomain}, supported: ${supported}`);
    }
    const _assets = initConfig.assets ?? [];
    for (const asset of _assets) {
        const assetDomains = [asset.canonical.domain].concat(Object.keys(asset.representations));
        const configuredDomains = supported.filter((domain) => assetDomains.includes(domain));
        if (JSON.stringify(configuredDomains) != JSON.stringify(supported)) {
            throw new Error(`Not configured asset domains, asset: ${asset.name}, canonical: (${asset.canonical.domain}, ${asset.canonical.address}), configured: ${configuredDomains}, parsed: ${domains}`);
        }
    }
    // get assets configuration for given domains
    const assets = _assets.map((asset) => {
        const _extracted = {
            name: asset.name,
            canonical: {
                domain: asset.canonical.domain,
                address: asset.canonical.address,
                decimals: asset.canonical.decimals,
                cap: asset.canonical.cap,
            },
            representations: {},
        };
        for (const domain of domains) {
            if (domain === hubDomain)
                continue;
            _extracted.representations[domain] = asset.representations[domain];
        }
        return _extracted;
    });
    /// MARK - Deployer
    // Get deployer mnemonic, which should be provided in env if not in the config.
    const privateKey = process.env.PRIVATE_KEY;
    const mnemonic = process.env.DEPLOYER || process.env.DEPLOYER_MNEMONIC || process.env.MNEMONIC;
    if (!mnemonic && !privateKey) {
        throw new Error("Deployer mnemonic was not specified. Please specify `deployer` in the config file, " +
            "or set DEPLOYER or DEPLOYER_MNEMONIC in env.");
    }
    const networks = [];
    const filteredHardhatNetworks = Object.values(config_1.hardhatNetworks).filter((hardhatNetwork) => Object.keys(hardhatNetwork).includes("chainId") &&
        Object.keys(hardhatNetwork).includes("url"));
    // Get deployments for each domain if not specified in the config.
    for (const _domain of domains) {
        const domain = _domain;
        const chainId = (0, domain_1.domainToChainId)(Number(domain));
        const chainConfig = Object.values(filteredHardhatNetworks).find((networkConfig) => networkConfig["chainId"] == chainId);
        if (!chainConfig || !chainConfig.url) {
            throw new Error(`Not configured network for chainId: ${chainId} in hardhat config`);
        }
        // Convert deployer from mnemonic to Wallet.
        let deployer;
        if (privateKey) {
            deployer = chainConfig.zksync ? new zk.Wallet(privateKey) : new ethers_1.Wallet(privateKey);
        }
        else {
            deployer = chainConfig.zksync ? zk.Wallet.fromMnemonic(mnemonic) : ethers_1.Wallet.fromMnemonic(mnemonic);
        }
        console.log("deployer: ", deployer.address);
        const rpc = chainConfig.zksync ? new zk.Provider(chainConfig.url) : new ethers_1.providers.JsonRpcProvider(chainConfig.url);
        const isHub = domain === hubDomain;
        const deployments = (0, helpers_1.getDeployments)({
            deployer,
            chainInfo: { chain: chainId.toString(), rpc, zksync: chainConfig.zksync || false },
            isHub,
            useStaging,
        });
        // TODO: relayers and agents should also be configured per-network. when this is done,
        // ensure the relayerFeeVault can be passed in
        networks.push({
            chain: chainId.toString(),
            domain,
            rpc,
            deployments,
            relayerFeeVault: deployments.messaging.RelayerProxy.address,
        });
    }
    const sanitized = {
        hub: hubDomain,
        networks,
        assets,
        agents: initConfig.agents,
    };
    console.log("Sanitized protocol config:", sanitized);
    console.log("DEBUG: Logging all deployment names:");
    for (const network of sanitized.networks) {
        console.log(network.chain, "deployments:", "\n" +
            Object.entries(network.deployments)
                .map(([k, v]) => {
                if (v.name) {
                    return `${k}: ${v.proxy}`;
                }
                else {
                    return Object.entries(network.deployments[k])
                        .map(([k, v]) => {
                        if (k === "HubConnectors") {
                            const list = v.map((c) => c.name).join(", ");
                            return `${k}: [ ${list} ]`;
                        }
                        return `${k}: ${v.proxy ?? v.name}`;
                    })
                        .join(";\n");
                }
            })
                .join(";\n"), "\n");
    }
    await (0, exports.initProtocol)(sanitized);
};
exports.sanitizeAndInit = sanitizeAndInit;
/**
 * Handle configuration of the entire protocol, including messaging stack and connext diamond
 * contracts, across all listed domains.
 *
 * Should effectively be a diagnostic on the whole protocol, making sure that everything that
 * requires configuration and/or setup has been done so properly.
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const initProtocol = async (protocol) => {
    /// ********************** SETUP **********************
    /// MARK - ChainData
    // Retrieve chain data for it to be saved locally; this will avoid those pesky logs and frontload the http request.
    const chainData = await (0, nxtp_utils_1.getChainData)(true);
    /// ********************* Messaging **********************
    /// MARK - Messaging
    await (0, messaging_1.setupMessaging)(protocol);
    /// ********************* CONNEXT *********************
    /// MARK - Enroll Handlers
    console.log("\n\nENROLLING HANDLERS");
    for (let i = 0; i < protocol.networks.length; i++) {
        const targetNetwork = protocol.networks[i];
        const remoteNetworks = protocol.networks.filter((_, j) => j !== i);
        for (const remoteNetwork of remoteNetworks) {
            const desiredConnextion = remoteNetwork.deployments.Connext.address;
            await (0, helpers_1.updateIfNeeded)({
                deployment: targetNetwork.deployments.Connext,
                desired: desiredConnextion,
                read: { method: "remote", args: [remoteNetwork.domain] },
                write: {
                    method: "enrollRemoteRouter",
                    args: [remoteNetwork.domain, ethers_1.utils.hexlify((0, domain_1.canonizeId)(desiredConnextion))],
                },
                chainData,
            });
        }
    }
    /// MARK - Set relayerFeeVault
    console.log("\n\nENROLLING RELAYER FEE VAULT");
    for (const network of protocol.networks) {
        const { relayerFeeVault, deployments: { Connext }, } = network;
        await (0, helpers_1.updateIfNeeded)({
            deployment: Connext,
            desired: relayerFeeVault,
            read: { method: "relayerFeeVault" },
            write: { method: "setRelayerFeeVault", args: [relayerFeeVault] },
            chainData,
        });
    }
    /// ********************* Relayer Proxy **********************
    /// MARK - relayer proxy
    console.log("\n\nCONFIGURE RELAYER PROXY");
    // On all domains, ensure the following are correctly set:
    // - connext
    // - spoke connector
    // - gelato relayer -- TODO: need to update config
    // - gelato fee collector -- TODO: need to update config
    for (const network of protocol.networks) {
        const isHub = network.domain === protocol.hub;
        const { Connext, messaging } = network.deployments;
        // update connext
        await (0, helpers_1.updateIfNeeded)({
            deployment: messaging.RelayerProxy,
            desired: Connext.address,
            read: { method: "connext" },
            write: { method: "setConnext", args: [Connext.address] },
            chainData,
        });
        // update spoke -- use MainnetConnector key if on hub
        const spokeConnector = isHub
            ? messaging.MainnetConnector.address
            : messaging.SpokeConnector.address;
        await (0, helpers_1.updateIfNeeded)({
            deployment: messaging.RelayerProxy,
            desired: spokeConnector,
            read: { method: "spokeConnector" },
            write: { method: "setSpokeConnector", args: [spokeConnector] },
            chainData,
        });
        // TODO: gelato relayer
        // TODO: gelato fee connector
        // On hub, ensure the following are correctly set:
        // - root manager
        if (isHub) {
            const rootManager = messaging.RootManager.address;
            await (0, helpers_1.updateIfNeeded)({
                deployment: messaging.RelayerProxy,
                desired: rootManager,
                read: { method: "rootManager" },
                write: { method: "setRootManager", args: [rootManager] },
                chainData,
            });
        }
    }
    /// ********************* ASSETS **********************
    /// MARK - Register Assets
    console.log("\n\nREGISTER ASSETS");
    // Convert asset addresses: get canonical ID, canonical domain, convert to `key` hash.
    // Determine if a stableswap pool is needed - does asset have both `local` and `adopted`?
    // If so, initialize stableswap pool with `initializeSwap`.
    // Call `setupAsset` for each domain. This will:
    // - Set up mappings for canonical ID / canonical domain / adopted asset address / etc.
    // - Set up mapping for stableswap pool if applicable.
    for (const asset of protocol.assets) {
        await (0, assets_1.setupAsset)({
            asset,
            networks: protocol.networks,
            chainData,
        });
    }
    /// ********************* AGENTS **********************
    if (protocol.agents) {
        /// MARK - Watchers
        if (protocol.agents.watchers) {
            if (protocol.agents.watchers.allowlist) {
                console.log("\n\nWHITELIST WATCHERS");
                // Get hub domain for specific use.
                const hub = protocol.networks.filter((d) => d.domain === protocol.hub)[0];
                /// MARK - Contracts
                // Convenience setup for contracts.
                const { WatcherManager } = hub.deployments.messaging;
                // Watchers are a permissioned role with the ability to disconnect malicious connectors.
                // Allowlist watchers in RootManager.
                for (const watcher of protocol.agents.watchers.allowlist) {
                    await (0, helpers_1.updateIfNeeded)({
                        deployment: WatcherManager,
                        desired: true,
                        read: { method: "isWatcher", args: [watcher] },
                        write: { method: "addWatcher", args: [watcher] },
                        chainData,
                    });
                }
            }
            // TODO: Blacklist/remove watchers.
        }
        /// MARK - Relayers
        if (protocol.agents.relayers) {
            if (protocol.agents.relayers.allowlist) {
                console.log("\n\nWHITELIST RELAYERS");
                for (const network of protocol.networks) {
                    const relayerProxyAddress = network.deployments.messaging.RelayerProxy.address;
                    await (0, helpers_1.updateIfNeeded)({
                        deployment: network.deployments.Connext,
                        desired: true,
                        read: { method: "approvedRelayers", args: [relayerProxyAddress] },
                        write: { method: "addRelayer", args: [relayerProxyAddress] },
                        chainData,
                    });
                    await (0, helpers_1.updateIfNeeded)({
                        deployment: network.deployments.messaging.RelayerProxy,
                        desired: nxtp_utils_1.GELATO_RELAYER_ADDRESS,
                        read: { method: "gelatoRelayer" },
                        write: { method: "setGelatoRelayer", args: [nxtp_utils_1.GELATO_RELAYER_ADDRESS] },
                        chainData,
                    });
                    const feeCollector = network.relayerFeeVault;
                    await (0, helpers_1.updateIfNeeded)({
                        deployment: network.deployments.messaging.RelayerProxy,
                        desired: feeCollector,
                        read: { method: "feeCollector" },
                        write: { method: "setFeeCollector", args: [feeCollector] },
                        chainData,
                    });
                }
                // Whitelist named relayers for the Relayer Proxy, in order to call `execute`.
                for (const relayer of protocol.agents.relayers.allowlist) {
                    for (const network of protocol.networks) {
                        await (0, helpers_1.updateIfNeeded)({
                            deployment: network.deployments.messaging.RelayerProxy,
                            desired: true,
                            read: { method: "allowedRelayer", args: [relayer] },
                            write: { method: "addRelayer", args: [relayer] },
                            chainData,
                        });
                        // also add relayers to the base connext contract
                        await (0, helpers_1.updateIfNeeded)({
                            deployment: network.deployments.Connext,
                            desired: true,
                            read: { method: "approvedRelayers", args: [relayer] },
                            write: { method: "addRelayer", args: [relayer] },
                            chainData,
                        });
                    }
                }
                // Additionally, approve relayers as callers for connectors and root manager.
            }
            // TODO: Blacklist/remove relayers.
        }
        /// MARK - Sequencers
        if (protocol.agents.sequencers) {
            if (protocol.agents.sequencers.allowlist) {
                console.log("\n\nWHITELIST SEQUENCERS");
                // Allowlist named sequencers.
                for (const sequencer of protocol.agents.sequencers.allowlist) {
                    for (const network of protocol.networks) {
                        await (0, helpers_1.updateIfNeeded)({
                            deployment: network.deployments.Connext,
                            desired: true,
                            read: { method: "approvedSequencers", args: [sequencer] },
                            write: { method: "addSequencer", args: [sequencer] },
                            chainData,
                        });
                    }
                }
            }
            // TODO: Blacklist/remove sequencers.
        }
        /// MARK - Routers
        if (protocol.agents.routers) {
            if (protocol.agents.routers.allowlist) {
                console.log("\n\nWHITELIST ROUTERS");
                // Allowlist connext routers.
                for (const router of protocol.agents.routers.allowlist) {
                    for (const network of protocol.networks) {
                        await (0, helpers_1.updateIfNeeded)({
                            deployment: network.deployments.Connext,
                            desired: true,
                            read: { method: "getRouterApproval", args: [router] },
                            // TODO: Should we enable configuring owner and recipient for this script, too?
                            write: { method: "approveRouter", args: [router] },
                            chainData,
                        });
                    }
                }
            }
            // TODO: Blacklist/remove routers.
        }
    }
};
exports.initProtocol = initProtocol;
//# sourceMappingURL=init.js.map