"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultLoansAPI = void 0;
const big_js_1 = __importDefault(require("big.js"));
const utils_1 = require("../utils");
const __1 = require("..");
class DefaultLoansAPI {
    constructor(api, assetRegistryAPI, transactionAPI) {
        this.api = api;
        this.assetRegistryAPI = assetRegistryAPI;
        this.transactionAPI = transactionAPI;
    }
    static parseMarket(market) {
        // TODO
        return Object.assign(Object.assign({}, market), { lendTokenId: market.lendTokenId.asLendToken.toNumber() });
    }
    // Wrapped call to make mocks in tests simple.
    getLoansMarketsEntries() {
        return __awaiter(this, void 0, void 0, function* () {
            const entries = yield this.api.query.loans.markets.entries();
            return entries.filter((entry) => entry[1].isSome);
        });
    }
    static getLendTokenFromUnderlyingCurrency(currency, lendTokenId) {
        return {
            name: `q${currency.name}`,
            ticker: `q${currency.ticker}`,
            decimals: currency.decimals,
            lendToken: {
                id: lendTokenId.asLendToken.toNumber(),
            },
        };
    }
    getUnderlyingCurrencyFromLendTokenId(lendTokenId) {
        return __awaiter(this, void 0, void 0, function* () {
            const underlyingCurrencyId = yield this.api.query.loans.underlyingAssetId(lendTokenId);
            const underlyingCurrency = yield utils_1.currencyIdToMonetaryCurrency(this.assetRegistryAPI, this, underlyingCurrencyId.unwrap());
            return underlyingCurrency;
        });
    }
    getLendTokenIdFromUnderlyingCurrency(currency) {
        return __awaiter(this, void 0, void 0, function* () {
            const currencyId = utils_1.newCurrencyId(this.api, currency);
            const { value } = yield this.api.query.loans.markets(currencyId);
            return value.lendTokenId;
        });
    }
    convertLendTokenToUnderlyingCurrency(amount, underlyingCurrencyId) {
        return __awaiter(this, void 0, void 0, function* () {
            const exchangeRate = yield this.api.query.loans.exchangeRate(underlyingCurrencyId);
            const decodedExchangeRate = utils_1.decodeFixedPointType(exchangeRate);
            return amount.mul(decodedExchangeRate);
        });
    }
    getLendAmountInUnderlyingCurrency(accountId, lendTokenId, underlyingCurrencyId) {
        return __awaiter(this, void 0, void 0, function* () {
            const lendTokenBalance = yield this.api.query.tokens.accounts(accountId, lendTokenId);
            const lendTokenBalanceTotal = lendTokenBalance.free.add(lendTokenBalance.reserved);
            const lendTokenBalanceInBig = big_js_1.default(lendTokenBalanceTotal.toString());
            return this.convertLendTokenToUnderlyingCurrency(lendTokenBalanceInBig, underlyingCurrencyId);
        });
    }
    getLendTokens() {
        return __awaiter(this, void 0, void 0, function* () {
            const marketEntries = yield this.getLoansMarketsEntries();
            return Promise.all(marketEntries.map(([key, market]) => __awaiter(this, void 0, void 0, function* () {
                const lendTokenId = market.unwrap().lendTokenId;
                const underlyingCurrencyId = utils_1.storageKeyToNthInner(key);
                const underlyingCurrency = yield utils_1.currencyIdToMonetaryCurrency(this.assetRegistryAPI, this, underlyingCurrencyId);
                return DefaultLoansAPI.getLendTokenFromUnderlyingCurrency(underlyingCurrency, lendTokenId);
            })));
        });
    }
    _getLendPosition(accountId, underlyingCurrency, underlyingCurrencyId, lendTokenId) {
        return __awaiter(this, void 0, void 0, function* () {
            const rewardCurrencyId = this.api.consts.loans.rewardAssetId;
            const underlyingCurrencyAmount = yield this.getLendAmountInUnderlyingCurrency(accountId, lendTokenId, underlyingCurrencyId);
            // Returns null if position does not exist
            if (underlyingCurrencyAmount.eq(0)) {
                return null;
            }
            const [accountEarned, accountDeposits, rewardAccrued, rewardCurrency, currentMarketStatus] = yield Promise.all([
                this.api.query.loans.accountEarned(underlyingCurrencyId, accountId),
                this.api.query.loans.accountDeposits(lendTokenId, accountId),
                // TODO: fix - This is returning reward per account basis, not per lend position
                this.api.query.loans.rewardAccrued(accountId),
                utils_1.currencyIdToMonetaryCurrency(this.assetRegistryAPI, this, rewardCurrencyId),
                this.api.rpc.loans.getMarketStatus(underlyingCurrencyId),
            ]);
            const startingExchangeRate = utils_1.decodeFixedPointType(accountEarned.exchangeRatePrior);
            const currentExchangeRate = utils_1.decodeFixedPointType(currentMarketStatus[2]);
            const earnedPrior = utils_1.decodeFixedPointType(accountEarned.totalEarnedPrior);
            const earnedInterest = currentExchangeRate
                .sub(startingExchangeRate)
                .mul(underlyingCurrencyAmount) // TODO: check whether to use underlying or lendtoken balance here
                .add(earnedPrior);
            const isCollateral = !accountDeposits.isZero();
            return {
                earnedInterest: utils_1.newMonetaryAmount(earnedInterest, underlyingCurrency),
                currency: underlyingCurrency,
                amount: utils_1.newMonetaryAmount(underlyingCurrencyAmount, underlyingCurrency),
                earnedReward: utils_1.newMonetaryAmount(rewardAccrued.toString(), rewardCurrency),
                isCollateral,
            };
        });
    }
    _calculateAccumulatedDebt(borrowedAmount, snapshotBorrowIndex, currentBorrowIndex) {
        if (snapshotBorrowIndex.eq(0)) {
            return big_js_1.default(0);
        }
        // @note Formula for computing total debt: https://docs.parallel.fi/parallel-finance/#2.6-interest-rate-index
        // To compute only earned debt, subtract 1 from factor
        const factor = currentBorrowIndex.div(snapshotBorrowIndex).sub(1);
        return borrowedAmount.mul(factor);
    }
    _getBorrowPosition(accountId, underlyingCurrency, underlyingCurrencyId) {
        return __awaiter(this, void 0, void 0, function* () {
            const [borrowSnapshot, marketStatus] = yield Promise.all([
                this.api.query.loans.accountBorrows(underlyingCurrencyId, accountId),
                this.api.rpc.loans.getMarketStatus(underlyingCurrencyId),
            ]);
            const borrowedAmount = big_js_1.default(borrowSnapshot.principal.toString());
            if (borrowedAmount.eq(0)) {
                return null;
            }
            const snapshotBorrowIndex = big_js_1.default(utils_1.decodeFixedPointType(borrowSnapshot.borrowIndex));
            const currentBorrowIndex = big_js_1.default(utils_1.decodeFixedPointType(marketStatus[6]));
            const accumulatedDebt = this._calculateAccumulatedDebt(borrowedAmount, snapshotBorrowIndex, currentBorrowIndex);
            return {
                amount: utils_1.newMonetaryAmount(borrowedAmount, underlyingCurrency),
                currency: underlyingCurrency,
                earnedReward: null,
                accumulatedDebt: utils_1.newMonetaryAmount(accumulatedDebt, underlyingCurrency),
            };
        });
    }
    _getPositionsOfAccount(accountId, getSinglePosition) {
        return __awaiter(this, void 0, void 0, function* () {
            const marketsEntries = yield this.getLoansMarketsEntries();
            const marketsCurrencies = marketsEntries.map(([key, value]) => [
                utils_1.storageKeyToNthInner(key),
                value.unwrap().lendTokenId,
            ]);
            const allMarketsPositions = yield Promise.all(marketsCurrencies.map(([underlyingCurrencyId, lendTokenId]) => __awaiter(this, void 0, void 0, function* () {
                const underlyingCurrency = yield utils_1.currencyIdToMonetaryCurrency(this.assetRegistryAPI, this, underlyingCurrencyId);
                return getSinglePosition(accountId, underlyingCurrency, underlyingCurrencyId, lendTokenId);
            })));
            return allMarketsPositions.filter((position) => position !== null);
        });
    }
    getLendPositionsOfAccount(accountId) {
        return __awaiter(this, void 0, void 0, function* () {
            return this._getPositionsOfAccount(accountId, this._getLendPosition.bind(this));
        });
    }
    getBorrowPositionsOfAccount(accountId) {
        return __awaiter(this, void 0, void 0, function* () {
            return this._getPositionsOfAccount(accountId, this._getBorrowPosition.bind(this));
        });
    }
    _getLendApy(underlyingCurrencyId) {
        return __awaiter(this, void 0, void 0, function* () {
            const rawLendApy = yield this.api.query.loans.supplyRate(underlyingCurrencyId);
            // Return percentage
            return utils_1.decodeFixedPointType(rawLendApy).mul(100);
        });
    }
    _getBorrowApy(underlyingCurrencyId) {
        return __awaiter(this, void 0, void 0, function* () {
            const rawBorrowApy = yield this.api.query.loans.borrowRate(underlyingCurrencyId);
            // Return percentage
            return utils_1.decodeFixedPointType(rawBorrowApy).mul(100);
        });
    }
    _getTotalLiquidityAndCapacity(underlyingCurrency, underlyingCurrencyId) {
        return __awaiter(this, void 0, void 0, function* () {
            const lendTokenId = yield this.getLendTokenIdFromUnderlyingCurrency(underlyingCurrency);
            const [lendTokenTotalIssuance, totalBorrows, exchangeRate] = yield Promise.all([
                this.api.query.tokens.totalIssuance(lendTokenId),
                yield this.api.query.loans.totalBorrows(underlyingCurrencyId),
                this.api.query.loans.exchangeRate(underlyingCurrencyId),
            ]);
            const decodedLendTokenTotalIssuance = big_js_1.default(lendTokenTotalIssuance.toString());
            const decodedTotalBorrows = big_js_1.default(totalBorrows.toString());
            const decodedExchangeRate = utils_1.decodeFixedPointType(exchangeRate);
            const totalLiquidity = decodedLendTokenTotalIssuance.mul(decodedExchangeRate);
            // @note Available capacity to borrow is being computed in a different way
            // than in the runtime: https://docs.parallel.fi/parallel-finance/#2.1-internal-exchange-rate
            const availableCapacity = totalLiquidity.sub(decodedTotalBorrows);
            const totalLiquidityMonetary = utils_1.newMonetaryAmount(totalLiquidity.toString(), underlyingCurrency);
            const availableCapacityMonetary = utils_1.newMonetaryAmount(availableCapacity.toString(), underlyingCurrency);
            return [totalLiquidityMonetary, availableCapacityMonetary];
        });
    }
    _getLendAndBorrowYearlyRewardAmount(underlyingCurrencyId) {
        return __awaiter(this, void 0, void 0, function* () {
            const [lendRewardSpeed, borrowRewardSpeed] = (yield Promise.all([
                this.api.query.loans.rewardSupplySpeed(underlyingCurrencyId),
                this.api.query.loans.rewardBorrowSpeed(underlyingCurrencyId),
            ])).map((rewardSpeed) => utils_1.decodeFixedPointType(rewardSpeed));
            const blockTime = this.api.consts.timestamp.minimumPeriod.toNumber() * 2;
            const blocksPerYear = (86400 * 365 * 1000) / blockTime;
            // @note could be refactored to compute APR in lib if we can get underlyingCurrency/rewardCurrency exchange rate,
            // but is it safe to assume that exchange rate for btc/underlyingCurrency will be
            // always fed to the oracle and available?
            // Return rate per 1 UNIT of underlying currency and compute APR
            // on UI where all exchange rates are available.
            const lendRewardAmount = lendRewardSpeed.mul(blocksPerYear);
            const borrowRewardAmount = borrowRewardSpeed.mul(blocksPerYear);
            return [lendRewardAmount, borrowRewardAmount];
        });
    }
    _getSubsidyReward(amount) {
        if (amount.eq(0)) {
            return null;
        }
        // Assumes native currency of parachain is always reward currency.
        const rewardCurrency = new __1.DefaultInterBtcApi(this.api).getGovernanceCurrency();
        return utils_1.newMonetaryAmount(amount, rewardCurrency);
    }
    _getLoanAsset(underlyingCurrencyId, marketData) {
        return __awaiter(this, void 0, void 0, function* () {
            const underlyingCurrency = yield utils_1.currencyIdToMonetaryCurrency(this.assetRegistryAPI, this, underlyingCurrencyId);
            const [lendApy, borrowApy, [totalLiquidity, availableCapacity], [lendRewardAmountYearly, borrowRewardAmountYearly],] = yield Promise.all([
                this._getLendApy(underlyingCurrencyId),
                this._getBorrowApy(underlyingCurrencyId),
                this._getTotalLiquidityAndCapacity(underlyingCurrency, underlyingCurrencyId),
                this._getLendAndBorrowYearlyRewardAmount(underlyingCurrencyId),
            ]);
            // Format data.
            const liquidationThreshold = utils_1.decodePermill(marketData.liquidationThreshold);
            const collateralThreshold = utils_1.decodePermill(marketData.collateralFactor);
            const lendReward = this._getSubsidyReward(lendRewardAmountYearly);
            const borrowReward = this._getSubsidyReward(borrowRewardAmountYearly);
            return [
                underlyingCurrency,
                {
                    isActive: marketData.state.isActive,
                    lendApy,
                    borrowApy,
                    currency: underlyingCurrency,
                    totalLiquidity,
                    availableCapacity,
                    liquidationThreshold,
                    collateralThreshold,
                    lendReward,
                    borrowReward,
                },
            ];
        });
    }
    getLoanAssets() {
        return __awaiter(this, void 0, void 0, function* () {
            const marketsEntries = yield this.getLoansMarketsEntries();
            const loanAssetsArray = yield Promise.all(marketsEntries.map(([key, marketData]) => this._getLoanAsset(utils_1.storageKeyToNthInner(key), marketData.unwrap())));
            const loanAssets = loanAssetsArray.reduce((result, [currency, loanAsset]) => (Object.assign(Object.assign({}, result), { [currency.ticker]: loanAsset })), {});
            return loanAssets;
        });
    }
    // Check that market for given currency is added and in active state.
    _checkMarketState(currency, action) {
        return __awaiter(this, void 0, void 0, function* () {
            const underlyingCurrencyId = utils_1.newCurrencyId(this.api, currency);
            const underlyingCurrencyMarket = yield this.api.query.loans.markets(underlyingCurrencyId);
            if (underlyingCurrencyMarket.isNone) {
                throw new Error(`Cannot ${action} currency that does not have market created`);
            }
            if (underlyingCurrencyMarket.unwrap().state.isPending) {
                throw new Error(`Cannot ${action} currency that does not have active market.`);
            }
        });
    }
    lend(underlyingCurrency, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this._checkMarketState(underlyingCurrency, "lend");
            const underlyingCurrencyId = utils_1.newCurrencyId(this.api, underlyingCurrency);
            const lendExtrinsic = this.api.tx.loans.mint(underlyingCurrencyId, amount.toString(true));
            yield this.transactionAPI.sendLogged(lendExtrinsic, this.api.events.loans.Deposited, true);
        });
    }
    withdraw(underlyingCurrency, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this._checkMarketState(underlyingCurrency, "withdraw");
            const underlyingCurrencyId = utils_1.newCurrencyId(this.api, underlyingCurrency);
            const withdrawExtrinsic = this.api.tx.loans.redeem(underlyingCurrencyId, amount.toString(true));
            yield this.transactionAPI.sendLogged(withdrawExtrinsic, this.api.events.loans.Redeemed, true);
        });
    }
    withdrawAll(underlyingCurrency) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this._checkMarketState(underlyingCurrency, "withdraw");
            const underlyingCurrencyId = utils_1.newCurrencyId(this.api, underlyingCurrency);
            const withdrawExtrinsic = this.api.tx.loans.redeemAll(underlyingCurrencyId);
            yield this.transactionAPI.sendLogged(withdrawExtrinsic, this.api.events.loans.Redeemed, true);
        });
    }
    enableAsCollateral(underlyingCurrency) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this._checkMarketState(underlyingCurrency, "enable collateral");
            const underlyingCurrencyId = utils_1.newCurrencyId(this.api, underlyingCurrency);
            const enableCollateralExtrinsic = this.api.tx.loans.depositAllCollateral(underlyingCurrencyId);
            yield this.transactionAPI.sendLogged(enableCollateralExtrinsic, this.api.events.loans.DepositCollateral, true);
        });
    }
    disableAsCollateral(underlyingCurrency) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this._checkMarketState(underlyingCurrency, "disable collateral");
            const underlyingCurrencyId = utils_1.newCurrencyId(this.api, underlyingCurrency);
            const enableCollateralExtrinsic = this.api.tx.loans.withdrawAllCollateral(underlyingCurrencyId);
            yield this.transactionAPI.sendLogged(enableCollateralExtrinsic, this.api.events.loans.WithdrawCollateral, true);
        });
    }
    claimAllSubsidyRewards() {
        return __awaiter(this, void 0, void 0, function* () {
            const claimRewardsExtrinsic = this.api.tx.loans.claimReward();
            yield this.transactionAPI.sendLogged(claimRewardsExtrinsic, this.api.events.loans.RewardPaid, true);
        });
    }
    borrow(underlyingCurrency, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this._checkMarketState(underlyingCurrency, "borrow");
            const underlyingCurrencyId = utils_1.newCurrencyId(this.api, underlyingCurrency);
            const borrowExtrinsic = this.api.tx.loans.borrow(underlyingCurrencyId, amount.toString(true));
            yield this.transactionAPI.sendLogged(borrowExtrinsic, this.api.events.loans.Borrowed, true);
        });
    }
    repay(underlyingCurrency, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this._checkMarketState(underlyingCurrency, "repay debt of");
            const underlyingCurrencyId = utils_1.newCurrencyId(this.api, underlyingCurrency);
            const repay = this.api.tx.loans.repayBorrow(underlyingCurrencyId, amount.toString(true));
            yield this.transactionAPI.sendLogged(repay, this.api.events.loans.RepaidBorrow, true);
        });
    }
    repayAll(underlyingCurrency) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this._checkMarketState(underlyingCurrency, "repay all debt of");
            const underlyingCurrencyId = utils_1.newCurrencyId(this.api, underlyingCurrency);
            const repayAllExtrinsic = this.api.tx.loans.repayBorrowAll(underlyingCurrencyId);
            yield this.transactionAPI.sendLogged(repayAllExtrinsic, this.api.events.loans.RepaidBorrow, true);
        });
    }
}
exports.DefaultLoansAPI = DefaultLoansAPI;
//# sourceMappingURL=loans.js.map