"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EVMRouter = void 0;
const crypto_wallet_core_1 = require("@bitpay-labs/crypto-wallet-core");
const cors_1 = __importDefault(require("cors"));
const express_1 = require("express");
const config_1 = __importDefault(require("../../../../config"));
const logger_1 = __importDefault(require("../../../../logger"));
const webhook_1 = require("../../../../models/webhook");
const config_2 = require("../../../../services/config");
const utils_1 = require("../../../../utils");
const opGasPriceOracle_1 = require("../abi/opGasPriceOracle");
const aave_1 = require("./aave");
const gnosis_1 = require("./gnosis");
class EVMRouter {
    constructor(csp, chain, params) {
        this.csp = csp;
        this.chain = chain?.toUpperCase();
        this.router = (0, express_1.Router)();
        this.router.param('network', (req, _res, next) => {
            const { network: beforeNetwork } = req.params;
            const { network } = config_2.Config.aliasFor({ chain: this.chain, network: beforeNetwork });
            req.params.network = network;
            next();
        });
        this.setDefaultRoutes(this.router);
        if (params?.multisig) {
            this.setMultiSigRoutes(this.router);
        }
        this.setWebhooks(this.router);
    }
    ;
    getRouter() {
        return this.router;
    }
    ;
    setDefaultRoutes(router) {
        this.getAccountNonce(router);
        this.estimateGas(router);
        this.getTokenInfo(router);
        this.getERC20TokenAllowance(router);
        this.getPriorityFee(router);
        this.estimateL1Fee(router);
        this.getAaveUserAccountData(router);
        this.getAaveReserveData(router);
        this.getAaveReserveTokensAddresses(router);
    }
    ;
    setMultiSigRoutes(router) {
        this.getMultisigEthInfo(router);
        this.getMultisigContractInstantiationInfo(router);
        this.getMultisigTxpsInfo(router);
        this.streamGnosisWalletTransactions(router);
    }
    ;
    setWebhooks(router) {
        this.postMoralisWebhook(router);
    }
    getAccountNonce(router) {
        router.get(`/api/${this.chain}/:network/address/:address/txs/count`, async (req, res) => {
            const { address, network } = req.params;
            try {
                const nonce = await this.csp.getAccountNonce(network, address);
                res.json({ nonce });
            }
            catch (err) {
                logger_1.default.error('Nonce Error::%o', err.stack || err.message || err);
                res.status(500).send(err.message || err);
            }
        });
    }
    ;
    estimateGas(router) {
        router.post(`/api/${this.chain}/:network/gas`, async (req, res) => {
            const { from, to, value, data } = req.body;
            const { network } = req.params;
            try {
                const gasLimit = await this.csp.estimateGas({ network, from, to, value, data });
                res.json(gasLimit);
            }
            catch (err) {
                if (err?.code != null) { // Preventable error from geth (probably due to insufficient funds or similar)
                    res.status(400).send(err.message);
                }
                else {
                    logger_1.default.error('Gas Error::%o', err.stack || err.message || err);
                    res.status(500).send(err.message || err);
                }
            }
        });
    }
    ;
    validateAaveRequest(requestedVersion, network, addressOrAsset) {
        if (!(0, aave_1.isAaveVersion)(requestedVersion)) {
            return 'Unsupported Aave version';
        }
        if (!(0, aave_1.getAavePoolAddress)(this.chain, network, requestedVersion)) {
            return 'Unsupported chain or network for Aave';
        }
        if (!crypto_wallet_core_1.Web3.utils.isAddress(addressOrAsset)) {
            return 'Invalid address';
        }
        return null;
    }
    ;
    getAaveUserAccountData(router) {
        router.get(`/api/${this.chain}/:network/aave/account/:address`, async (req, res) => {
            const { address, network } = req.params;
            const requestedVersion = String(req.query.version || 'v3').toLowerCase();
            const error = this.validateAaveRequest(requestedVersion, network, address);
            if (error) {
                res.status(400).send(error);
                return;
            }
            try {
                const accountData = await this.csp.getAaveUserAccountData({ network, address, version: requestedVersion });
                res.json(accountData);
            }
            catch (err) {
                logger_1.default.error('Aave getUserAccountData error::%o', err.stack || err.message || err);
                res.status(500).send(err.message || err);
            }
        });
    }
    ;
    getAaveReserveData(router) {
        router.get(`/api/${this.chain}/:network/aave/reserve/:asset`, async (req, res) => {
            const { network, asset } = req.params;
            const requestedVersion = String(req.query.version || 'v3').toLowerCase();
            const error = this.validateAaveRequest(requestedVersion, network, asset);
            if (error) {
                res.status(400).send(error);
                return;
            }
            try {
                const data = await this.csp.getAaveReserveData({ network, asset, version: requestedVersion });
                res.json(data);
            }
            catch (err) {
                logger_1.default.error('Aave getReserveData error::%o', err.stack || err.message || err);
                res.status(500).send(err.message || err);
            }
        });
    }
    ;
    getAaveReserveTokensAddresses(router) {
        router.get(`/api/${this.chain}/:network/aave/reserve-tokens/:asset`, async (req, res) => {
            const { network, asset } = req.params;
            const requestedVersion = String(req.query.version || 'v3').toLowerCase();
            const error = this.validateAaveRequest(requestedVersion, network, asset);
            if (error) {
                res.status(400).send(error);
                return;
            }
            try {
                const data = await this.csp.getAaveReserveTokensAddresses({ network, asset, version: requestedVersion });
                res.json(data);
            }
            catch (err) {
                logger_1.default.error('Aave getReserveTokensAddresses error::%o', err.stack || err.message || err);
                res.status(500).send(err.message || err);
            }
        });
    }
    ;
    estimateL1Fee(router) {
        router.post(`/api/${this.chain}/:network/l1/fee`, async (req, res) => {
            try {
                const { network } = req.params;
                const { rawTx } = req.body;
                const { safe } = req.query;
                const { needsL1Fee } = config_2.Config.chainConfig({ chain: this.chain, network });
                if (!needsL1Fee) {
                    return res.json('0'); // No L1 fee required
                }
                if (!rawTx) {
                    return res.status(400).send('unsigned `rawTx` is required');
                }
                // Reference: https://docs.optimism.io/builders/app-developers/transactions/estimates
                const packedRawTx = crypto_wallet_core_1.Web3.utils.encodePacked(rawTx);
                const rawTxBuf = Buffer.from(packedRawTx.slice(2), 'hex');
                const { web3 } = await this.csp.getWeb3(network);
                const gasPriceOracle = new web3.eth.Contract(opGasPriceOracle_1.OPGasPriceOracleAbi, opGasPriceOracle_1.OPGasPriceOracleAddress);
                let l1DataFee;
                if ((0, utils_1.castToBool)(safe)) {
                    l1DataFee = await gasPriceOracle.methods.getL1FeeUpperBound(rawTxBuf.length).call();
                }
                else {
                    l1DataFee = await gasPriceOracle.methods.getL1Fee(rawTxBuf).call();
                }
                return res.json(l1DataFee.toString()); // this is the total L1 fee in wei (i.e. L1 feeRate * gas).
            }
            catch (err) {
                logger_1.default.error('L1 Fee Error::%o', err.stack || err.message || err);
                return res.status(500).send(err.message || err);
            }
        });
    }
    getTokenInfo(router) {
        router.get(`/api/${this.chain}/:network/token/:tokenAddress`, async (req, res) => {
            const { network, tokenAddress } = req.params;
            try {
                const tokenInfo = await this.csp.getERC20TokenInfo(network, tokenAddress);
                res.json(tokenInfo);
            }
            catch (err) {
                logger_1.default.error('Token Info Error::%o', err.stack || err.message || err);
                res.status(500).send(err.message || err);
            }
        });
    }
    ;
    getERC20TokenAllowance(router) {
        router.get(`/api/${this.chain}/:network/token/:tokenAddress/allowance/:ownerAddress/for/:spenderAddress`, async (req, res) => {
            const { network, tokenAddress, ownerAddress, spenderAddress } = req.params;
            try {
                const allowance = await this.csp.getERC20TokenAllowance(network, tokenAddress, ownerAddress, spenderAddress);
                res.json(allowance);
            }
            catch (err) {
                logger_1.default.error('Token Allowance Error::%o', err.stack || err.message || err);
                res.status(500).send(err.message || err);
            }
        });
    }
    ;
    getPriorityFee(router) {
        router.get(`/api/${this.chain}/:network/priorityFee/:percentile`, async (req, res) => {
            let { network } = req.params;
            const { percentile } = req.params;
            const priorityFeePercentile = Number(percentile) || 15;
            network = network.toLowerCase();
            try {
                const fee = await this.csp.getPriorityFee({ network, percentile: priorityFeePercentile });
                return res.json(fee);
            }
            catch (err) {
                logger_1.default.error('Fee Error: %o', err.stack || err.message || err);
                return res.status(500).send('Error getting priority fee from RPC');
            }
        });
    }
    ;
    streamGnosisWalletTransactions(router) {
        router.get(`/api/${this.chain}/:network/ethmultisig/transactions/:multisigContractAddress`, async (req, res) => {
            const { network, multisigContractAddress } = req.params;
            try {
                return await gnosis_1.Gnosis.streamGnosisWalletTransactions({
                    chain: this.chain,
                    network,
                    multisigContractAddress,
                    wallet: {},
                    req,
                    res,
                    args: req.query
                });
            }
            catch (err) {
                logger_1.default.error('Multisig Transactions Error::%o', err.stack || err.message || err);
                return res.status(500).send(err.message || err);
            }
        });
    }
    ;
    getMultisigTxpsInfo(router) {
        router.get(`/api/${this.chain}/:network/ethmultisig/txps/:multisigContractAddress`, async (req, res) => {
            const { network, multisigContractAddress } = req.params;
            try {
                const multisigTxpsInfo = await gnosis_1.Gnosis.getMultisigTxpsInfo(this.chain, network, multisigContractAddress);
                res.json(multisigTxpsInfo);
            }
            catch (err) {
                logger_1.default.error('Multisig Txps Error::%o', err.stack || err.message || err);
                res.status(500).send(err.message || err);
            }
        });
    }
    ;
    getMultisigContractInstantiationInfo(router) {
        router.get(`/api/${this.chain}/:network/ethmultisig/:sender/instantiation/:txId`, async (req, res) => {
            const { network, sender, txId } = req.params;
            try {
                const multisigInstantiationInfo = await gnosis_1.Gnosis.getMultisigContractInstantiationInfo(this.chain, network, sender, txId);
                res.json(multisigInstantiationInfo);
            }
            catch (err) {
                logger_1.default.error('Multisig Instantiation Error::%o', err.stack || err.message || err);
                res.status(500).send(err.message || err);
            }
        });
    }
    ;
    getMultisigEthInfo(router) {
        router.get(`/api/${this.chain}/:network/ethmultisig/info/:multisigContractAddress`, async (req, res) => {
            const { network, multisigContractAddress } = req.params;
            try {
                const multisigInfo = await gnosis_1.Gnosis.getMultisigInfo(this.chain, network, multisigContractAddress);
                res.json(multisigInfo);
            }
            catch (err) {
                logger_1.default.error('Multisig Info Error::%o', err.stack || err.message || err);
                res.status(500).send(err.message || err);
            }
        });
    }
    ;
    _validateMoralisWebhook(req, res, next) {
        const secret = config_1.default.externalProviders?.moralis?.streamSecret;
        if (!secret) {
            return res.status(404).send('Moralis not configured');
        }
        const reqSig = req.headers['x-signature'];
        if (!reqSig) {
            return res.status(400).send('Signature not provided');
        }
        const computedSig = crypto_wallet_core_1.Web3.utils.sha3(JSON.stringify(req.body) + secret);
        if (reqSig !== computedSig) {
            return res.status(406).send('Unauthorized');
        }
        next();
    }
    postMoralisWebhook(router) {
        const webhookCors = config_1.default.externalProviders?.moralis?.webhookCors;
        router.post(`/webhook/${this.chain}/:network/moralis`, (0, cors_1.default)(webhookCors), this._validateMoralisWebhook, async (req, res) => {
            try {
                const { network } = req.params;
                if (req.body.chainId === '') {
                    // This is a webhook test call from moralis
                    return res.end();
                }
                await webhook_1.WebhookStorage.collection.insertOne({
                    chain: this.chain,
                    network,
                    source: 'moralis',
                    sourceId: req.body.streamId,
                    tag: req.body.tag,
                    body: req.body,
                    timestamp: new Date(),
                    processed: false
                });
                return res.end();
            }
            catch (err) {
                logger_1.default.error('Error processing moralis webhook: %o', err.stack || err.message || err);
                return res.status(500).send('Unable to process webhook');
            }
        });
    }
}
exports.EVMRouter = EVMRouter;
//# sourceMappingURL=routes.js.map