"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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultIssueAPI = void 0;
const utils_1 = require("../utils");
const types_1 = require("../types");
class DefaultIssueAPI {
    constructor(api, btcNetwork, electrsAPI, wrappedCurrency, feeAPI, vaultsAPI, transactionAPI) {
        this.api = api;
        this.btcNetwork = btcNetwork;
        this.electrsAPI = electrsAPI;
        this.wrappedCurrency = wrappedCurrency;
        this.feeAPI = feeAPI;
        this.vaultsAPI = vaultsAPI;
        this.transactionAPI = transactionAPI;
    }
    getRequestLimits(vaults) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!vaults)
                vaults = yield this.vaultsAPI.getVaultsWithIssuableTokens();
            const vaultsArr = [...vaults.entries()].sort(
            // sort in descending order
            ([_id_1, amount_1], [_id_2, amount_2]) => amount_2.sub(amount_1).toBig().toNumber());
            if (vaultsArr.length === 0) {
                return {
                    singleVaultMaxIssuable: utils_1.newMonetaryAmount(0, this.wrappedCurrency),
                    totalMaxIssuable: utils_1.newMonetaryAmount(0, this.wrappedCurrency),
                };
            }
            const singleVaultMaxIssuable = vaultsArr[0][1];
            const totalMaxIssuable = vaultsArr.reduce((total, [_, vaultAvailable]) => {
                return total.add(vaultAvailable);
            }, utils_1.newMonetaryAmount(0, this.wrappedCurrency));
            return { singleVaultMaxIssuable, totalMaxIssuable };
        });
    }
    /**
     * @param events The EventRecord array returned after sending an issue request transaction
     * @returns The issueId associated with the request. If the EventRecord array does not
     * contain issue request events, the function throws an error.
     */
    getIssueIdsFromEvents(events) {
        return utils_1.getRequestIdsFromEvents(events, this.api.events.issue.RequestIssue, this.api);
    }
    request(amount, vaultAccountId, collateralCurrencyIdLiteral, atomic = true, retries = 0, cachedVaults) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                if (vaultAccountId) {
                    if (!collateralCurrencyIdLiteral) {
                        return Promise.reject(new Error("A collateral currency must be specified along with the vault account ID"));
                    }
                    // If a vault account id is defined, request to issue with that vault only.
                    // Initialize the `amountsPerVault` map with a single entry, the (vaultId, amount) pair
                    const collateralCurrencyId = utils_1.newCurrencyId(this.api, collateralCurrencyIdLiteral);
                    const vaultId = utils_1.newVaultId(this.api, vaultAccountId.toString(), types_1.currencyIdToMonetaryCurrency(collateralCurrencyId), this.wrappedCurrency);
                    const amountsPerVault = new Map([[vaultId, amount]]);
                    return yield this.requestAdvanced(amountsPerVault, atomic);
                }
                const availableVaults = cachedVaults || (yield this.vaultsAPI.getVaultsWithIssuableTokens());
                const amountsPerVault = utils_1.allocateAmountsToVaults(availableVaults, amount);
                const result = yield this.requestAdvanced(amountsPerVault, atomic);
                const successfulSum = result.reduce((sum, req) => sum.add(req.wrappedAmount), utils_1.newMonetaryAmount(0, this.wrappedCurrency));
                const remainder = amount.sub(successfulSum);
                if (remainder.isZero() || retries === 0)
                    return result;
                else {
                    return (yield this.request(remainder, vaultAccountId, collateralCurrencyIdLiteral, atomic, retries - 1, availableVaults)).concat(result);
                }
            }
            catch (e) {
                return Promise.reject(e);
            }
        });
    }
    craftRequestTx(vaultId, amount) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.api.tx.issue.requestIssue(amount.toString(amount.currency.rawBase), vaultId);
        });
    }
    requestAdvanced(amountsPerVault, atomic) {
        return __awaiter(this, void 0, void 0, function* () {
            const txs = yield Promise.all(Array.from(amountsPerVault.entries()).map(([vaultId, amount]) => new Promise((resolve) => {
                this.craftRequestTx(vaultId, amount).then(resolve);
            })));
            // batchAll fails atomically, batch allows partial successes
            const batch = (atomic ? this.api.tx.utility.batchAll : this.api.tx.utility.batch)(txs);
            try {
                const result = yield this.transactionAPI.sendLogged(batch, this.api.events.issue.RequestIssue);
                const ids = this.getIssueIdsFromEvents(result.events);
                const issueRequests = yield this.getRequestsByIds(ids);
                return issueRequests;
            }
            catch (e) {
                return Promise.reject(e);
            }
        });
    }
    execute(requestId, btcTxId) {
        return __awaiter(this, void 0, void 0, function* () {
            const parsedRequestId = utils_1.ensureHashEncoded(this.api, requestId);
            const txInclusionDetails = yield utils_1.getTxProof(this.electrsAPI, btcTxId);
            const tx = this.api.tx.issue.executeIssue(parsedRequestId, txInclusionDetails.merkleProof, txInclusionDetails.rawTx);
            yield this.transactionAPI.sendLogged(tx, this.api.events.issue.ExecuteIssue, true);
        });
    }
    cancel(requestId) {
        return __awaiter(this, void 0, void 0, function* () {
            const parsedRequestId = this.api.createType("H256", utils_1.addHexPrefix(requestId));
            const cancelIssueTx = this.api.tx.issue.cancelIssue(parsedRequestId);
            yield this.transactionAPI.sendLogged(cancelIssueTx, this.api.events.issue.CancelIssue, true);
        });
    }
    setIssuePeriod(blocks) {
        return __awaiter(this, void 0, void 0, function* () {
            const period = this.api.createType("BlockNumber", blocks);
            const tx = this.api.tx.sudo.sudo(this.api.tx.issue.setIssuePeriod(period));
            yield this.transactionAPI.sendLogged(tx, undefined, true);
        });
    }
    getIssuePeriod() {
        return __awaiter(this, void 0, void 0, function* () {
            const blockNumber = yield this.api.query.issue.issuePeriod();
            return blockNumber.toNumber();
        });
    }
    list() {
        return __awaiter(this, void 0, void 0, function* () {
            const issueRequests = yield this.api.query.issue.issueRequests.entries();
            return yield Promise.all(issueRequests
                .filter(([_, req]) => req.isSome.valueOf())
                // Can be unwrapped because the filter removes `None` values
                .map(([id, req]) => utils_1.parseIssueRequest(this.vaultsAPI, req.unwrap(), this.btcNetwork, utils_1.storageKeyToNthInner(id))));
        });
    }
    getFeesToPay(amount) {
        return __awaiter(this, void 0, void 0, function* () {
            const feePercentage = yield this.getFeeRate();
            return amount.mul(feePercentage);
        });
    }
    getDustValue() {
        return __awaiter(this, void 0, void 0, function* () {
            const dustValueSat = yield this.api.query.issue.issueBtcDustValue();
            return utils_1.newMonetaryAmount(dustValueSat.toString(), this.wrappedCurrency);
        });
    }
    getFeeRate() {
        return __awaiter(this, void 0, void 0, function* () {
            const issueFee = yield this.api.query.fee.issueFee();
            return utils_1.decodeFixedPointType(issueFee);
        });
    }
    getRequestById(issueId) {
        return __awaiter(this, void 0, void 0, function* () {
            return (yield this.getRequestsByIds([issueId]))[0];
        });
    }
    getRequestsByIds(issueIds) {
        return __awaiter(this, void 0, void 0, function* () {
            const head = yield this.api.rpc.chain.getFinalizedHead();
            const issueRequestData = yield Promise.all(issueIds.map((issueId) => __awaiter(this, void 0, void 0, function* () {
                return new Promise((resolve, reject) => {
                    this.api.query.issue.issueRequests
                        .at(head, utils_1.ensureHashEncoded(this.api, issueId))
                        .then((request) => resolve([request, issueId]))
                        .catch(reject);
                });
            })));
            return Promise.all(issueRequestData
                .filter(([option, _]) => option.isSome)
                .map(([issueRequest, issueId]) => utils_1.parseIssueRequest(this.vaultsAPI, issueRequest.unwrap(), this.btcNetwork, issueId)));
        });
    }
    getVaultIssuableAmount(vaultAccountId, collateralCurrency) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.vaultsAPI.getIssueableTokensFromVault(vaultAccountId, collateralCurrency);
        });
    }
}
exports.DefaultIssueAPI = DefaultIssueAPI;
//# sourceMappingURL=issue.js.map