"use strict";
/**
 * @file cToken
 * @desc These methods facilitate interactions with the cToken smart
 *     contracts.
 */
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.protectedMint = exports.calcBorrowAmountForProtectedMint = exports.liquidateBorrow = exports.repayBorrowWithPermit = exports.repayBorrow = exports.borrow = exports.redeem = exports.supplyWithPermit = exports.supply = void 0;
const ethers_1 = require("ethers");
const eth = __importStar(require("./eth"));
const helpers_1 = require("./helpers");
const constants_1 = require("./constants");
const abi_1 = require("./abi");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const typechain_1 = require("./typechain");
const util_1 = require("./util");
const toSDRSymbol = (name) => {
    if (!name.startsWith('sdr')) {
        return 'sdr' + name;
    }
    return name;
};
/**
 * Supplies the user's Ethereum asset to the Sumer Protocol.
 *
 * @param {string} asset A string of the asset to supply.
 * @param {number | string} amount A string, number
 *     object of the amount of an asset to supply. Use the `mantissa` boolean in
 *     the `options` parameter to indicate if this value is scaled up (so there
 *     are no decimals) or in its natural scale.
 * @param {boolean} noApprove Explicitly prevent this method from attempting an
 *     ERC-20 `approve` transaction prior to sending the `mint` transaction.
 * @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 supply
 *     transaction.
 *
 * @example
 *
 * ```
 * const sumer = new Sumer(window.ethereum);
 *
 * // Ethers.js overrides are an optional 3rd parameter for `supply`
 * // const trxOptions = { gasLimit: 250000, mantissa: false };
 *
 * (async function() {
 *
 *   console.log('Supplying ETH to the Sumer Protocol...');
 *   const trx = await sumer.supply(Sumer.ETH, 1);
 *   console.log('Ethers.js transaction object', trx);
 *
 * })().catch(console.error);
 * ```
 */
async function supply(asset, amount, noApprove = false, options = {}) {
    await (0, helpers_1.netId)(this);
    const errorPrefix = 'Sumer [supply] | ';
    console.log(`Sumer supply ${amount} ${asset}`);
    const cTokenName = toSDRSymbol(asset);
    const cTokenAddress = (0, constants_1.getAddress)(this._network.name, cTokenName);
    if (!cTokenAddress || !(0, constants_1.isUnderlyAllowed)(this._network.name, asset)) {
        throw Error(errorPrefix + `Asset ${asset} cannot be supplied.`);
    }
    if (typeof amount !== 'number' && typeof amount !== 'string') {
        throw Error(errorPrefix + 'Argument `amount` must be a string, number.');
    }
    const assetDecimals = (0, constants_1.getDecimals)(this._network.name, asset);
    let amountBN = new bignumber_js_1.default(amount.toString());
    if (!options.mantissa) {
        amountBN = amountBN.times(`1e${assetDecimals}`);
    }
    if ((0, constants_1.isEther)(this._network.name, cTokenName)) {
        options.abi = abi_1.abi.CEther;
    }
    else {
        options.abi = abi_1.abi.CErc20;
    }
    options._sumerProvider = this._provider;
    if (!(0, constants_1.isEther)(this._network.name, cTokenName) && noApprove !== true) {
        const underlyingAddress = (0, constants_1.getAddress)(this._network.name, asset);
        let userAddress = this._provider.address;
        if (!userAddress && this._provider.getAddress) {
            userAddress = await this._provider.getAddress();
        }
        // Check allowance
        const allowance = await eth.read(underlyingAddress, 'allowance', [userAddress, cTokenAddress], options);
        const notEnough = amountBN.gt(String(allowance));
        if (notEnough) {
            // ERC-20 approve transaction
            await (await eth.trx(underlyingAddress, 'approve', [cTokenAddress, amountBN.toFixed()], options)).wait();
        }
    }
    const parameters = [];
    if ((0, constants_1.isEther)(this._network.name, cTokenName)) {
        options.value = amountBN.toFixed();
    }
    else {
        parameters.push(amountBN.toFixed());
    }
    console.log(`Call mint on ${cTokenName}:${cTokenAddress} with ${amountBN.toString()} `);
    return eth.trx(cTokenAddress, 'mint', parameters, options);
}
exports.supply = supply;
async function supplyWithPermit(asset, amount, options = {}) {
    const errorPrefix = 'Sumer [supplyWithPermit] | ';
    console.log(`Sumer supplyWithPermit ${amount} ${asset}`);
    const tokenAddress = (0, constants_1.getAddress)(this._network.name, asset);
    const cTokenName = toSDRSymbol(asset);
    const cTokenAddress = (0, constants_1.getAddress)(this._network.name, cTokenName);
    if (!cTokenAddress || !(0, constants_1.isUnderlyAllowed)(this._network.name, asset)) {
        throw Error(errorPrefix + `Asset ${asset} cannot be supplied.`);
    }
    if (typeof amount !== 'number' && typeof amount !== 'string') {
        throw Error(errorPrefix + 'Argument `amount` must be a string, number.');
    }
    const assetDecimals = (0, constants_1.getDecimals)(this._network.name, asset);
    let amountBN = new bignumber_js_1.default(amount.toString());
    if (!options.mantissa) {
        amountBN = amountBN.times(`1e${assetDecimals}`);
    }
    options.abi = abi_1.abi.CErc20;
    options._sumerProvider = this._provider;
    const parameters = [];
    const fixedAmount = amountBN.toFixed();
    const tokenSymbol = (0, util_1.getName)(tokenAddress, this._network.name);
    const { signature, deadline } = await (0, util_1.getSignature)({
        signer: this._provider,
        tokenSymbol,
        chainId: this._network.id,
        verifyingContract: tokenAddress,
        spender: cTokenAddress,
        value: fixedAmount,
    });
    parameters.push(fixedAmount, deadline, signature);
    const method = 'mintWithPermit';
    console.log(`Call ${method} on ${cTokenName}:${cTokenAddress} with ${amountBN.toString()} `);
    return eth.trx(cTokenAddress, method, parameters, options);
}
exports.supplyWithPermit = supplyWithPermit;
/**
 * Redeems the user's Ethereum asset from the Sumer Protocol.
 *
 * @param {string} asset A string of the asset to redeem, or its cToken name.
 * @param {number | string} amount A string, number
 *     object of the amount of an asset to redeem. Use the `mantissa` boolean in
 *     the `options` parameter to indicate if this value is scaled up (so there
 *     are no decimals) or in its natural scale. This can be an amount of
 *     cTokens or underlying asset (use the `asset` parameter to specify).
 * @param {CallOptions} [options] Call options and Ethers.js overrides for the
 *     transaction.
 *
 * @returns {object} Returns an Ethers.js transaction object of the redeem
 *     transaction.
 *
 * @example
 *
 * ```
 * const sumer = new Sumer(window.ethereum);
 *
 * (async function() {
 *
 *   console.log('Redeeming ETH...');
 *   const trx = await sumer.redeem(Sumer.ETH, 1); // also accepts cToken args
 *   console.log('Ethers.js transaction object', trx);
 *
 * })().catch(console.error);
 * ```
 */
async function redeem(asset, amount, options = {}) {
    await (0, helpers_1.netId)(this);
    const errorPrefix = 'Sumer [redeem] | ';
    console.log(`Sumer redeem ${amount} ${asset}`);
    if (typeof asset !== 'string' || asset.length < 1) {
        throw Error(errorPrefix + 'Argument `asset` must be a non-empty string.');
    }
    const assetIsCToken = asset.includes('sdr');
    const cTokenName = toSDRSymbol(asset);
    const cTokenAddress = (0, constants_1.getAddress)(this._network.name, cTokenName);
    const underlyingName = assetIsCToken ? asset.slice(3, asset.length) : asset;
    if (!(0, constants_1.isCTokenAllowed)(this._network.name, cTokenName) || !(0, constants_1.isUnderlyAllowed)(this._network.name, underlyingName)) {
        throw Error(errorPrefix + `Asset '${cTokenName}' is not supported.`);
    }
    if (typeof amount !== 'number' && typeof amount !== 'string') {
        throw Error(errorPrefix + 'Argument `amount` must be a string, number.');
    }
    let amountBN = new bignumber_js_1.default(amount.toString());
    if (!options.mantissa) {
        const assetDecimals = (0, constants_1.getDecimals)(this._network.name, asset);
        if (assetDecimals <= 0) {
            throw Error(`Asset ${asset} decimals is configured wrong as ${assetDecimals} `);
        }
        amountBN = amountBN.times(`1e${assetDecimals}`);
    }
    const trxOptions = Object.assign(Object.assign({}, options), { _sumerProvider: this._provider, abi: (0, constants_1.isEther)(this._network.name, cTokenName) ? abi_1.abi.CEther : abi_1.abi.CErc20 });
    const parameters = [amountBN.toFixed()];
    const method = assetIsCToken ? 'redeem' : 'redeemUnderlying';
    console.log(`Call ${method} on ${cTokenName}:${cTokenAddress} with ${amountBN.toString()}`);
    return eth.trx(cTokenAddress, method, parameters, trxOptions);
}
exports.redeem = redeem;
/**
 * Borrows an Ethereum asset from the Sumer Protocol for the user. The user's
 *     address must first have supplied collateral and entered a corresponding
 *     market.
 *
 * @param {string} asset A string of the asset to borrow (must be a supported
 *     underlying asset).
 * @param {number | string} amount A string, number
 *     object of the amount of an asset to borrow. Use the `mantissa` boolean in
 *     the `options` parameter to indicate if this value is scaled up (so there
 *     are no decimals) or in its natural scale.
 * @param {CallOptions} [options] Call options and Ethers.js overrides for the
 *     transaction.
 *
 * @returns {object} Returns an Ethers.js transaction object of the borrow
 *     transaction.
 *
 * @example
 *
 * ```
 * const sumer = new Sumer(window.ethereum);
 *
 * (async function() {
 *
 *   const daiScaledUp = '32000000000000000000';
 *   const trxOptions = { mantissa: true };
 *
 *   console.log('Borrowing 32 Dai...');
 *   const trx = await sumer.borrow(Sumer.DAI, daiScaledUp, trxOptions);
 *
 *   console.log('Ethers.js transaction object', trx);
 *
 * })().catch(console.error);
 * ```
 */
async function borrow(asset, amount, options = {}) {
    await (0, helpers_1.netId)(this);
    const errorPrefix = 'Sumer [borrow] | ';
    console.log(`Sumer borrow ${amount} ${asset}`);
    const cTokenName = toSDRSymbol(asset);
    const cTokenAddress = (0, constants_1.getAddress)(this._network.name, cTokenName);
    if (!cTokenAddress || !(0, constants_1.isUnderlyAllowed)(this._network.name, asset)) {
        throw Error(errorPrefix + 'Argument `asset` cannot be borrowed.');
    }
    if (typeof amount !== 'number' && typeof amount !== 'string') {
        throw Error(errorPrefix + 'Argument `amount` must be a string, number.');
    }
    let amountBN = new bignumber_js_1.default(amount.toString());
    if (!options.mantissa) {
        const assetDecimals = (0, constants_1.getDecimals)(this._network.name, asset);
        if (assetDecimals <= 0) {
            throw Error(`Asset ${asset} decimals is configured wrong as ${assetDecimals} `);
        }
        amountBN = amountBN.times(`1e${assetDecimals}`);
    }
    const trxOptions = Object.assign(Object.assign({}, options), { _sumerProvider: this._provider });
    const parameters = [amountBN.toFixed()];
    trxOptions.abi = (0, constants_1.isEther)(this._network.name, cTokenName) ? abi_1.abi.CEther : abi_1.abi.CErc20;
    console.log(`Call borrow on ${cTokenName}:${cTokenAddress} with ${amount.toString()}`);
    return eth.trx(cTokenAddress, 'borrow', parameters, trxOptions);
}
exports.borrow = borrow;
/**
 * Repays a borrowed Ethereum asset for the user or on behalf of another
 *     Ethereum address.
 *
 * @param {string} asset A string of the asset that was borrowed (must be a
 *     supported underlying asset).
 * @param {number | string} amount A string, number
 *     object of the amount of an asset to borrow. Use the `mantissa` boolean in
 *     the `options` parameter to indicate if this value is scaled up (so there
 *     are no decimals) or in its natural scale.
 * @param {string | null} [borrower] The Ethereum address of the borrower to
 *     repay an open borrow for. Set this to `null` if the user is repaying
 *     their own borrow.
 * @param {boolean} noApprove Explicitly prevent this method from attempting an
 *     ERC-20 `approve` transaction prior to sending the subsequent repayment
 *     transaction.
 * @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 `repayBorrow` or `repayBorrowBehalf` transactions.
 *
 * @returns {object} Returns an Ethers.js transaction object of the repayBorrow
 *     or repayBorrowBehalf transaction.
 *
 * @example
 *
 * ```
 * const sumer = new Sumer(window.ethereum);
 *
 * (async function() {
 *
 *   console.log('Repaying Dai borrow...');
 *   const address = null; // set this to any address to repayBorrowBehalf
 *   const trx = await sumer.repayBorrow(Sumer.DAI, 32, address);
 *
 *   console.log('Ethers.js transaction object', trx);
 *
 * })().catch(console.error);
 * ```
 */
async function repayBorrow(asset, amount, borrower, noApprove = false, options = {}) {
    await (0, helpers_1.netId)(this);
    const errorPrefix = 'Sumer [repayBorrow] | ';
    console.log(`Sumer repayBorrow ${amount} ${asset} for ${borrower}`);
    const cTokenName = toSDRSymbol(asset);
    const cTokenAddress = (0, constants_1.getAddress)(this._network.name, cTokenName);
    if (!cTokenAddress || !(0, constants_1.isUnderlyAllowed)(this._network.name, asset)) {
        throw Error(errorPrefix + 'Argument `asset` is not supported.');
    }
    if (typeof amount !== 'number' && typeof amount !== 'string') {
        throw Error(errorPrefix + 'Argument `amount` must be a string, number.');
    }
    const method = ethers_1.ethers.isAddress(borrower) ? 'repayBorrowBehalf' : 'repayBorrow';
    if (borrower && method === 'repayBorrow') {
        throw Error(errorPrefix + 'Invalid `borrower` address.');
    }
    const assetDecimals = (0, constants_1.getDecimals)(this._network.name, asset);
    if (assetDecimals <= 0) {
        throw Error(`Asset ${asset} decimals is configured wrong as ${assetDecimals} `);
    }
    let amountBN = new bignumber_js_1.default(amount.toString());
    if (!options.mantissa) {
        amountBN = amountBN.times(`1e${assetDecimals}`);
    }
    const trxOptions = Object.assign(Object.assign({}, options), { _sumerProvider: this._provider });
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const parameters = method === 'repayBorrowBehalf' ? [borrower] : [];
    if ((0, constants_1.isEther)(this._network.name, cTokenName)) {
        trxOptions.value = amountBN.toFixed();
        trxOptions.abi = abi_1.abi.CEther;
    }
    else {
        parameters.push(amountBN.toFixed());
        trxOptions.abi = abi_1.abi.CErc20;
    }
    if (!(0, constants_1.isEther)(this._network.name, cTokenName) && noApprove !== true) {
        const underlyingAddress = (0, constants_1.getAddress)(this._network.name, asset);
        let userAddress = this._provider.address;
        if (!userAddress && this._provider.getAddress) {
            userAddress = await this._provider.getAddress();
        }
        // Check allowance
        const allowance = await eth.read(underlyingAddress, 'allowance', [userAddress, cTokenAddress], trxOptions);
        const notEnough = amountBN.gt(String(allowance));
        if (notEnough) {
            // ERC-20 approve transaction
            await (await eth.trx(underlyingAddress, 'approve', [cTokenAddress, amountBN.toFixed()], trxOptions)).wait();
        }
    }
    console.log(`Call repayBorrow on ${cTokenName}:${cTokenAddress} with ${amountBN.toString()}`);
    return eth.trx(cTokenAddress, method, parameters, trxOptions);
}
exports.repayBorrow = repayBorrow;
async function repayBorrowWithPermit(asset, amount, options = {}) {
    await (0, helpers_1.netId)(this);
    const errorPrefix = 'Sumer [repayBorrowWithPermit] | ';
    console.log(`Sumer repayBorrowWithPermit ${amount} ${asset}`);
    const cTokenName = toSDRSymbol(asset);
    const tokenAddress = (0, constants_1.getAddress)(this._network.name, asset);
    const cTokenAddress = (0, constants_1.getAddress)(this._network.name, cTokenName);
    if (!cTokenAddress || !(0, constants_1.isUnderlyAllowed)(this._network.name, asset)) {
        throw Error(errorPrefix + 'Argument `asset` is not supported.');
    }
    if (typeof amount !== 'number' && typeof amount !== 'string') {
        throw Error(errorPrefix + 'Argument `amount` must be a string, number.');
    }
    const assetDecimals = (0, constants_1.getDecimals)(this._network.name, asset);
    if (assetDecimals <= 0) {
        throw Error(`Asset ${asset} decimals is configured wrong as ${assetDecimals} `);
    }
    let amountBN = new bignumber_js_1.default(amount.toString());
    if (!options.mantissa) {
        amountBN = amountBN.times(`1e${assetDecimals}`);
    }
    const trxOptions = Object.assign(Object.assign({}, options), { _sumerProvider: this._provider });
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const parameters = [];
    trxOptions.abi = abi_1.abi.CErc20;
    const fixedAmount = amountBN.toFixed();
    const { signature, deadline } = await (0, util_1.getSignature)({
        signer: this._provider,
        tokenSymbol: asset,
        chainId: this._network.id,
        verifyingContract: tokenAddress,
        spender: cTokenAddress,
        value: fixedAmount,
    });
    parameters.push(fixedAmount, deadline, signature);
    console.log(`Call repayBorrowWithPermit on ${cTokenName}:${cTokenAddress} with ${amountBN.toString()}`);
    return eth.trx(cTokenAddress, 'repayBorrowWithPermit', parameters, trxOptions);
}
exports.repayBorrowWithPermit = repayBorrowWithPermit;
async function liquidateBorrow(repayToken, borrower, amount, cTokenCollateral) {
    const cerc20 = typechain_1.CErc20__factory.connect(repayToken, this._provider);
    return cerc20.liquidateBorrow(borrower, amount, cTokenCollateral);
}
exports.liquidateBorrow = liquidateBorrow;
async function calcBorrowAmountForProtectedMint(asset, collateral, mintAmount, options = {}) {
    const errorPrefix = 'Sumer [calcBorrowAmountForProtectedMint] | ';
    console.log(`Sumer calcBorrowAmountForProtectedMint ${mintAmount} ${asset} with collateral ${collateral}`);
    const suTokenName = toSDRSymbol(asset);
    const suTokenAddress = (0, constants_1.getAddress)(this._network.name, suTokenName);
    if (!suTokenAddress || !(0, constants_1.isUnderlyAllowed)(this._network.name, asset)) {
        throw Error(errorPrefix + 'Argument `asset` is not supported.');
    }
    const cTokenName = toSDRSymbol(collateral);
    const cTokenAddress = (0, constants_1.getAddress)(this._network.name, cTokenName);
    if (!cTokenAddress || !(0, constants_1.isUnderlyAllowed)(this._network.name, collateral)) {
        throw Error(errorPrefix + 'Argument `collateral` is not supported.');
    }
    const assetDecimals = (0, constants_1.getDecimals)(this._network.name, asset);
    if (assetDecimals <= 0) {
        throw Error(`Asset ${asset} decimals is configured wrong as ${assetDecimals} `);
    }
    let amountBN = new bignumber_js_1.default(mintAmount.toString());
    amountBN = amountBN.times(`1e${assetDecimals}`);
    options.abi = abi_1.abi.CompoundLens;
    const trxOptions = Object.assign(Object.assign({}, options), { _sumerProvider: this._provider });
    const compoundLensAddress = (0, constants_1.getAddress)(this._network.name, 'CompoundLens');
    let userAddress = this._provider.address;
    if (!userAddress && this._provider.getAddress) {
        userAddress = await this._provider.getAddress();
    }
    return await eth.read(compoundLensAddress, 'calcBorrowAmountForProtectedMint', [userAddress, cTokenAddress, suTokenAddress, amountBN.toFixed(0)], trxOptions);
}
exports.calcBorrowAmountForProtectedMint = calcBorrowAmountForProtectedMint;
async function protectedMint(asset, collateral, cBorrowAmount, mintAmount, options = {}) {
    await (0, helpers_1.netId)(this);
    const errorPrefix = 'Sumer [protectedMint] | ';
    console.log(`Sumer protectedMint ${mintAmount} ${asset} with collateral ${collateral}`);
    const suTokenName = toSDRSymbol(asset);
    const suTokenAddress = (0, constants_1.getAddress)(this._network.name, suTokenName);
    if (!suTokenAddress || !(0, constants_1.isUnderlyAllowed)(this._network.name, asset)) {
        throw Error(errorPrefix + 'Argument `asset` is not supported.');
    }
    const cTokenName = toSDRSymbol(collateral);
    const cTokenAddress = (0, constants_1.getAddress)(this._network.name, cTokenName);
    if (!cTokenAddress || !(0, constants_1.isUnderlyAllowed)(this._network.name, collateral)) {
        throw Error(errorPrefix + 'Argument `collateral` is not supported.');
    }
    const collateralDecimals = (0, constants_1.getDecimals)(this._network.name, collateral);
    if (collateralDecimals <= 0) {
        throw Error(`Collateral ${collateral} decimals is configured wrong as ${collateralDecimals} `);
    }
    let cBorrowAmountBN = new bignumber_js_1.default(cBorrowAmount.toString());
    cBorrowAmountBN = cBorrowAmountBN.times(`1e${collateralDecimals}`);
    if (typeof mintAmount !== 'number' && typeof mintAmount !== 'string') {
        throw Error(errorPrefix + 'Argument `mintAmount` must be a string, number.');
    }
    const assetDecimals = (0, constants_1.getDecimals)(this._network.name, asset);
    if (assetDecimals <= 0) {
        throw Error(`Asset ${asset} decimals is configured wrong as ${assetDecimals} `);
    }
    let amountBN = new bignumber_js_1.default(mintAmount.toString());
    amountBN = amountBN.times(`1e${assetDecimals}`);
    options.abi = abi_1.abi.suErc20;
    const trxOptions = Object.assign(Object.assign({}, options), { _sumerProvider: this._provider });
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const parameters = [cTokenAddress, cBorrowAmountBN.toFixed(0), amountBN.toFixed(0)];
    console.log(`Call protectedMint on ${suTokenName}:${suTokenAddress} with [${cTokenAddress}, ${amountBN.toFixed(0)}]`);
    return eth.trx(suTokenAddress, 'protectedMint', parameters, trxOptions);
}
exports.protectedMint = protectedMint;
//# sourceMappingURL=cToken.js.map