"use strict";
/**
 * @file Comptroller
 * @desc These methods facilitate interactions with the Comptroller smart
 *     contract.
 */
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.calcPlan = exports.redeemFaceValuePreview = exports.redeemFaceValueWithPermit = exports.redeemFaceValue = exports.exitMarket = exports.enterMarkets = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const eth = __importStar(require("./eth"));
const helpers_1 = require("./helpers");
const constants_1 = require("./constants");
const abi_1 = require("./abi");
const axios_1 = __importDefault(require("axios"));
const util_1 = require("./util");
const typechain_1 = require("./typechain");
const _1 = __importDefault(require("."));
const toSDRSymbol = (name) => {
    if (!name.startsWith('sdr')) {
        return 'sdr' + name;
    }
    return name;
};
const toUnderlySymbol = (name) => {
    if (name.startsWith('sdr')) {
        return name.slice(3);
    }
    return name;
};
/**
 * Enters the user's address into Sumer Protocol markets.
 *
 * @param {any[]} markets An array of strings of markets to enter, meaning use
 *     those supplied assets as collateral.
 * @param {CallOptions} [options] Call options and Ethers.js overrides for the
 *     transaction. A passed `gasLimit` will be used in both the `approve` (if
 *     not supressed) and `mint` transactions.
 *
 * @returns {object} Returns an Ethers.js transaction object of the enterMarkets
 *     transaction.
 *
 * @example
 *
 * ```
 * const sumer = new Sumer(window.ethereum);
 *
 * (async function () {
 *   const trx = await sumer.enterMarkets(Sumer.ETH); // Use [] for multiple
 *   console.log('Ethers.js transaction object', trx);
 * })().catch(console.error);
 * ```
 */
async function enterMarkets(markets = [], options = {}) {
    await (0, helpers_1.netId)(this);
    const errorPrefix = 'Sumer [enterMarkets] | ';
    if (typeof markets === 'string') {
        markets = [markets];
    }
    if (!Array.isArray(markets)) {
        throw Error(errorPrefix + 'Argument `markets` must be an array or string.');
    }
    const addresses = [];
    for (let i = 0; i < markets.length; i++) {
        markets[i] = toSDRSymbol(markets[i]);
        if (!(0, constants_1.isCTokenAllowed)(this._network.name, markets[i])) {
            throw Error(errorPrefix + 'Provided market `' + markets[i] + '` is not a recognized cToken.');
        }
        const cTokenAddress = (0, constants_1.getAddress)(this._network.name, markets[i]);
        addresses.push(cTokenAddress.toLowerCase());
    }
    const comptrollerAddress = (0, constants_1.getAddress)(this._network.name, 'Comptroller');
    const parameters = [addresses];
    // const comptroller = Comptroller__factory.connect(comptrollerAddress, this._provider)
    const trxOptions = Object.assign({ _sumerProvider: this._provider, abi: abi_1.abi.Comptroller }, options);
    // return (await comptroller.enterMarkets(addresses)).getTransaction
    return eth.trx(comptrollerAddress, 'enterMarkets', parameters, trxOptions);
}
exports.enterMarkets = enterMarkets;
/**
 * Exits the user's address from a Sumer Protocol market.
 *
 * @param {string} market A string of the symbol of the market to exit.
 * @param {CallOptions} [options] Call options and Ethers.js overrides for the
 *     transaction. A passed `gasLimit` will be used in both the `approve` (if
 *     not supressed) and `mint` transactions.
 *
 * @returns {object} Returns an Ethers.js transaction object of the exitMarket
 *     transaction.
 *
 * @example
 *
 * ```
 * const sumer = new Sumer(window.ethereum);
 *
 * (async function () {
 *   const trx = await sumer.exitMarket(Sumer.ETH);
 *   console.log('Ethers.js transaction object', trx);
 * })().catch(console.error);
 * ```
 */
async function exitMarket(market, options = {}) {
    await (0, helpers_1.netId)(this);
    const errorPrefix = 'Sumer [exitMarket] | ';
    if (typeof market !== 'string' || market === '') {
        throw Error(errorPrefix + 'Argument `market` must be a string of a cToken market name.');
    }
    market = toSDRSymbol(market);
    if (!(0, constants_1.isCTokenAllowed)(this._network.name, market)) {
        throw Error(errorPrefix + 'Provided market `' + market + '` is not a recognized cToken.');
    }
    const cTokenAddress = (0, constants_1.getAddress)(this._network.name, market);
    const comptrollerAddress = (0, constants_1.getAddress)(this._network.name, 'Comptroller');
    const parameters = [cTokenAddress.toLowerCase()];
    const trxOptions = Object.assign({ _sumerProvider: this._provider, abi: abi_1.abi.Comptroller }, options);
    return eth.trx(comptrollerAddress, 'exitMarket', parameters, trxOptions);
}
exports.exitMarket = exitMarket;
async function redeemFaceValue(market, amount, providers, deadline, signature, options = {}) {
    await (0, helpers_1.netId)(this);
    const errorPrefix = 'Sumer [redeemFaceValue] | ';
    if (typeof market !== 'string' || market === '') {
        throw Error(errorPrefix + 'Argument `market` must be a string of a cToken market name.');
    }
    market = toSDRSymbol(market);
    if (!(0, constants_1.isCTokenAllowed)(this._network.name, market)) {
        throw Error(errorPrefix + 'Provided market `' + market + '` is not a recognized cToken.');
    }
    const cTokenAddress = (0, constants_1.getAddress)(this._network.name, market);
    const redemptionManagerAddress = (0, constants_1.getAddress)(this._network.name, 'RedemptionManager');
    const parameters = [cTokenAddress.toLowerCase(), amount, providers, deadline, signature];
    const trxOptions = Object.assign({ _sumerProvider: this._provider, abi: abi_1.abi.RedemptionManager }, options);
    return eth.trx(redemptionManagerAddress, 'redeemFaceValue', parameters, trxOptions);
}
exports.redeemFaceValue = redeemFaceValue;
async function redeemFaceValueWithPermit(market, amount, providers, providersDeadline, providersSignature, options = {}) {
    await (0, helpers_1.netId)(this);
    const errorPrefix = 'Sumer [redeemFaceValueWithPermit] | ';
    if (typeof market !== 'string' || market === '') {
        throw Error(errorPrefix + 'Argument `market` must be a string of a cToken market name.');
    }
    const cTokenName = toSDRSymbol(market);
    if (!(0, constants_1.isCTokenAllowed)(this._network.name, cTokenName)) {
        throw Error(errorPrefix + 'Provided market `' + cTokenName + '` is not a recognized cToken.');
    }
    const tokenAddress = (0, constants_1.getAddress)(this._network.name, market);
    const cTokenAddress = (0, constants_1.getAddress)(this._network.name, cTokenName);
    const { signature, deadline } = await (0, util_1.getSignature)({
        signer: this._provider,
        tokenSymbol: market,
        chainId: this._network.id,
        verifyingContract: tokenAddress,
        spender: cTokenAddress,
        value: amount,
    });
    const parameters = [
        cTokenAddress.toLowerCase(),
        amount,
        providers,
        providersDeadline,
        providersSignature,
        deadline,
        signature,
    ];
    const trxOptions = Object.assign({ _sumerProvider: this._provider, abi: abi_1.abi.RedemptionManager }, options);
    const redemptionManagerAddress = (0, constants_1.getAddress)(this._network.name, 'RedemptionManager');
    return eth.trx(redemptionManagerAddress, 'redeemFaceValueWithPermit', parameters, trxOptions);
}
exports.redeemFaceValueWithPermit = redeemFaceValueWithPermit;
async function redeemFaceValuePreview(chainId, redeemer, market, amount) {
    const response = await axios_1.default.post(`https://api-sumer.sumer.money/api/${chainId}/${market}/redemptionPreview`, {
        redeemer,
        amount,
    });
    return response.data;
}
exports.redeemFaceValuePreview = redeemFaceValuePreview;
async function calcPlan(account, seizeTokenAddress, repayTokenAddress) {
    const comptrollerAddress = (0, constants_1.getAddress)(this._network.name, 'Comptroller');
    const comptroller = typechain_1.Comptroller__factory.connect(comptrollerAddress, this._provider);
    const seizeToken = typechain_1.CErc20__factory.connect(seizeTokenAddress, this._provider);
    const repayToken = typechain_1.CErc20__factory.connect(repayTokenAddress, this._provider);
    // read price data
    const oracleAddress = await comptroller.oracle();
    const oracle = typechain_1.FeedPriceOracle__factory.connect(oracleAddress, this._provider);
    const prices = await oracle.getUnderlyingPrices([seizeTokenAddress, repayTokenAddress]);
    const seizeTokenPrice = new bignumber_js_1.default(prices[0].toString()).div(1e18); // unit: 1
    const repayTokenPrice = new bignumber_js_1.default(prices[1].toString()).div(1e18); // unit: 1
    // read groupId
    const seizeMarket = await comptroller.markets(seizeTokenAddress);
    const repayMarket = await comptroller.markets(repayTokenAddress);
    const seizeGroupId = Number(seizeMarket[1]);
    const repayGroupId = Number(repayMarket[1]);
    // read is ctoken
    const repayIsCToken = await repayToken.isCToken();
    // read exchange rate
    const exchangeRate = new bignumber_js_1.default((await seizeToken.exchangeRateCurrent.staticCall()).toString()).div(1e18); // unit: 1
    // read minCloseValue and closeFactor
    const globalConfig = await comptroller.globalConfig();
    const minCloseValue = new bignumber_js_1.default(globalConfig[1].toString());
    const closeFactor = new bignumber_js_1.default(globalConfig[0].toString()).div(1e18); // unit: 1
    console.log(`minCloseValue: ${minCloseValue}, closeFactor: ${closeFactor}`);
    // protocol share
    const protocolShare = new bignumber_js_1.default(await seizeToken.protocolSeizeShareMantissa()).div(1e18); // unit:1
    const userShare = new bignumber_js_1.default(1).minus(protocolShare);
    // read liquidation incentive
    const incentive = await comptroller.liquidationIncentive();
    let liquidationIncentive = new bignumber_js_1.default(incentive[0].toString()).div(1e4);
    if (repayGroupId == seizeGroupId) {
        if (repayIsCToken) {
            liquidationIncentive = new bignumber_js_1.default(incentive[1].toString()).div(1e4);
        }
        else {
            liquidationIncentive = new bignumber_js_1.default(incentive[2].toString()).div(1e4);
        }
    }
    console.log(`liquidation incentive: ${liquidationIncentive}`);
    const seizePair = _1.default.getTokenPairByAddress(this._network.name, seizeTokenAddress);
    const repayPair = _1.default.getTokenPairByAddress(this._network.name, repayTokenAddress);
    const seizeDecimals = seizePair.ctokenDecimal;
    const repayDecimals = repayPair.ctokenDecimal;
    const seizeSymbol = seizePair.ctokenSym;
    const repaySymbol = repayPair.ctokenSym;
    const seizeSnapshot = await seizeToken.getAccountSnapshot(account);
    const repaySnapshot = await repayToken.getAccountSnapshot(account);
    const seizePosSupply = new bignumber_js_1.default(seizeSnapshot[0].toString()).div(`1e${seizeDecimals}`);
    const repayPosBorrow = new bignumber_js_1.default(repaySnapshot[1].toString()).div(`1e${repayDecimals}`);
    // const price = this.priceListener.getPriceFor(pos.tokenAddress);
    console.log(`seize - ${seizeSymbol} deposit: ${seizePosSupply}, decimals: ${seizeDecimals}, price: ${seizeTokenPrice}`);
    console.log(`repay - ${repaySymbol} borrow: ${repayPosBorrow}, decimals: ${repayDecimals}, price: ${repayTokenPrice}`);
    const maxSeizeTokens = new bignumber_js_1.default(seizePosSupply).times(exchangeRate);
    const maxSeizeUSD = maxSeizeTokens.times(seizeTokenPrice);
    const actualCloseFactor = closeFactor.isGreaterThan(1) ? 1 : closeFactor;
    let maxRepayTokens = repayPosBorrow.times(actualCloseFactor);
    let maxRepayUSD = maxRepayTokens.times(repayTokenPrice);
    if (maxRepayUSD.lt(minCloseValue)) {
        maxRepayTokens = repayPosBorrow;
        maxRepayUSD = repayPosBorrow.times(repayTokenPrice);
    }
    let profitUSD = new bignumber_js_1.default(0);
    let actualRepay = new bignumber_js_1.default(0);
    let actualSeize = new bignumber_js_1.default(0);
    console.log(`maxRepayUSD:${maxRepayUSD.toFixed()}, maxSeizeUSD:${maxSeizeUSD.toFixed()}`);
    const actualMaxRepayUSD = maxRepayUSD.times(liquidationIncentive.plus(1));
    if (actualMaxRepayUSD.gt(maxSeizeUSD)) {
        console.log(`use maxSeizeUSD to calculate`);
        actualSeize = maxSeizeTokens.div(liquidationIncentive.plus(1)).times(liquidationIncentive.times(userShare).plus(1));
        actualRepay = maxSeizeUSD.div(liquidationIncentive.plus(1)).div(repayTokenPrice);
        profitUSD = maxSeizeUSD.div(liquidationIncentive.plus(1)).times(liquidationIncentive).times(userShare);
        console.log(`actual seize: ${actualSeize}`);
        console.log(`actual repay: ${actualRepay}, liquidationIncentive: ${liquidationIncentive}, ${userShare}`);
        console.log(`repayTokenPrice: ${repayTokenPrice}  ${repayTokenAddress}`);
        console.log(`actualRepay: ${actualRepay.toFixed()}, profit:${profitUSD.toFixed()} `);
    }
    else {
        console.log(`use maxRepayUSD to calculate`);
        actualRepay = maxRepayTokens;
        actualSeize = maxRepayUSD.times(liquidationIncentive.times(userShare).plus(1)).div(seizeTokenPrice);
        profitUSD = maxRepayUSD.times(liquidationIncentive).times(userShare);
        console.log(`actual repay: ${actualRepay}`);
        console.log(`seizeTokenPrice: ${seizeTokenPrice}, ${seizeTokenAddress}`);
        console.log(`actual seize: ${actualSeize}, liquidationIncentive: ${liquidationIncentive}, ${userShare}`);
        console.log(`actualSeize: ${actualRepay.toFixed()}, profit:${profitUSD.toFixed()} `);
    }
    const plan = {
        account,
        repayTokenPrice: repayTokenPrice.toFixed(),
        repayTokenAddress,
        repayTokens: actualRepay.times(`1e${repayDecimals}`).toFixed(0),
        seizeTokenPrice: seizeTokenPrice.toFixed(),
        seizeTokenAddress,
        seizeTokens: actualSeize.times(`1e${seizeDecimals}`).toFixed(0),
        profitUSD: profitUSD.toNumber(),
    };
    return plan;
}
exports.calcPlan = calcPlan;
//# sourceMappingURL=comptroller.js.map