"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.getIsTransferCompletedAlgorand = exports.getIsTransferCompletedSolana = exports.getIsTransferCompletedTerra = exports.getIsTransferCompletedEth = exports.getIsTransferCompletedAlph = void 0;
const web3_js_1 = require("@solana/web3.js");
const algosdk_1 = require("algosdk");
const axios_1 = __importDefault(require("axios"));
const _1 = require(".");
const __1 = require("..");
const utils_1 = require("../utils");
const web3_1 = require("@alephium/web3");
const algorand_1 = require("../algorand");
const bridge_1 = require("../bridge");
const ethers_contracts_1 = require("../ethers-contracts");
const wasm_1 = require("../solana/wasm");
const bigint_1 = require("../utils/bigint");
const alephium_1 = require("./alephium");
const TokenBridgeForChain_1 = require("../alephium-contracts/ts/TokenBridgeForChain");
const UnexecutedSequence_1 = require("../alephium-contracts/ts/UnexecutedSequence");
const bigInt512 = BigInt(512);
const bigInt256 = BigInt(256);
const bigInt1 = BigInt(1);
function isSequenceExecuted(tokenBridgeForChainId, sequence, groupIndex) {
    return __awaiter(this, void 0, void 0, function* () {
        const path = alephium_1.zeroPad(Math.floor(Number(sequence) / 256).toString(16), 8);
        const contractId = web3_1.subContractId(tokenBridgeForChainId, path, groupIndex);
        const contractAddress = web3_1.addressFromContractId(contractId);
        try {
            const contract = UnexecutedSequence_1.UnexecutedSequence.at(contractAddress);
            const contractState = yield contract.fetchState();
            const distance = sequence - contractState.fields.begin;
            return ((contractState.fields.sequences >> distance) & bigInt1) === bigInt1;
        }
        catch (error) {
            if (error instanceof Error && error.message.includes('KeyNotFound')) {
                // the unexecuted contract has been destroyed
                return true;
            }
            throw error;
        }
    });
}
function getIsTransferCompletedAlph(tokenBridgeForChainId, groupIndex, signedVAA) {
    return __awaiter(this, void 0, void 0, function* () {
        const tokenBridgeForChainAddress = web3_1.addressFromContractId(tokenBridgeForChainId);
        const contract = TokenBridgeForChain_1.TokenBridgeForChain.at(tokenBridgeForChainAddress);
        const contractState = yield contract.fetchState();
        const sequence = utils_1.extractSequenceFromVAA(signedVAA);
        if (sequence < contractState.fields.start) {
            return isSequenceExecuted(tokenBridgeForChainId, sequence, groupIndex);
        }
        let distance = sequence - contractState.fields.start;
        if (distance >= bigInt512) {
            return false;
        }
        if (distance < bigInt256) {
            return ((contractState.fields.firstNext256 >> distance) & bigInt1) === bigInt1;
        }
        distance = distance - bigInt256;
        return ((contractState.fields.secondNext256 >> distance) & bigInt1) === bigInt1;
    });
}
exports.getIsTransferCompletedAlph = getIsTransferCompletedAlph;
function getIsTransferCompletedEth(tokenBridgeAddress, provider, signedVAA) {
    return __awaiter(this, void 0, void 0, function* () {
        const tokenBridge = ethers_contracts_1.Bridge__factory.connect(tokenBridgeAddress, provider);
        const signedVAAHash = yield bridge_1.getSignedVAAHash(signedVAA);
        return yield tokenBridge.isTransferCompleted(signedVAAHash);
    });
}
exports.getIsTransferCompletedEth = getIsTransferCompletedEth;
function getIsTransferCompletedTerra(tokenBridgeAddress, signedVAA, client, gasPriceUrl) {
    return __awaiter(this, void 0, void 0, function* () {
        const msg = yield _1.redeemOnTerra(tokenBridgeAddress, __1.TERRA_REDEEMED_CHECK_WALLET_ADDRESS, signedVAA);
        // TODO: remove gasPriceUrl and just use the client's gas prices
        const gasPrices = yield axios_1.default.get(gasPriceUrl).then((result) => result.data);
        const account = yield client.auth.accountInfo(__1.TERRA_REDEEMED_CHECK_WALLET_ADDRESS);
        try {
            yield client.tx.estimateFee([
                {
                    sequenceNumber: account.getSequenceNumber(),
                    publicKey: account.getPublicKey(),
                },
            ], {
                msgs: [msg],
                memo: "already redeemed calculation",
                feeDenoms: ["uluna"],
                gasPrices,
            });
        }
        catch (e) {
            // redeemed if the VAA was already executed
            return e.response.data.message.includes("VaaAlreadyExecuted");
        }
        return false;
    });
}
exports.getIsTransferCompletedTerra = getIsTransferCompletedTerra;
function getIsTransferCompletedSolana(tokenBridgeAddress, signedVAA, connection) {
    return __awaiter(this, void 0, void 0, function* () {
        const { claim_address } = yield wasm_1.importCoreWasm();
        const claimAddress = yield claim_address(tokenBridgeAddress, signedVAA);
        const claimInfo = yield connection.getAccountInfo(new web3_js_1.PublicKey(claimAddress), "confirmed");
        return !!claimInfo;
    });
}
exports.getIsTransferCompletedSolana = getIsTransferCompletedSolana;
// Algorand
/**
 * This function is used to check if a VAA has been redeemed by looking at a specific bit.
 * @param client AlgodV2 client
 * @param appId Application Id
 * @param addr Wallet address. Someone has to pay for this.
 * @param seq The sequence number of the redemption
 * @returns true, if the bit was set and VAA was redeemed, false otherwise.
 */
function checkBitsSet(client, appId, addr, seq) {
    return __awaiter(this, void 0, void 0, function* () {
        let retval = false;
        let appState = [];
        const acctInfo = yield client.accountInformation(addr).do();
        const als = acctInfo["apps-local-state"];
        als.forEach((app) => {
            if (BigInt(app["id"]) === appId) {
                appState = app["key-value"];
            }
        });
        if (appState.length === 0) {
            return retval;
        }
        const BIG_MAX_BITS = BigInt(algorand_1.MAX_BITS);
        const BIG_EIGHT = BigInt(8);
        // Start on a MAX_BITS boundary
        const start = (seq / BIG_MAX_BITS) * BIG_MAX_BITS;
        // beg should be in the range [0..MAX_BITS]
        const beg = bigint_1.safeBigIntToNumber(seq - start);
        // s should be in the range [0..15]
        const s = Math.floor(beg / algorand_1.BITS_PER_KEY);
        const b = Math.floor((beg - s * algorand_1.BITS_PER_KEY) / 8);
        const key = Buffer.from(algosdk_1.bigIntToBytes(s, 1)).toString("base64");
        appState.forEach((kv) => {
            if (kv["key"] === key) {
                const v = Buffer.from(kv["value"]["bytes"], "base64");
                const bt = 1 << bigint_1.safeBigIntToNumber(seq % BIG_EIGHT);
                retval = (v[b] & bt) != 0;
                return;
            }
        });
        return retval;
    });
}
/**
 * <p>Returns true if this transfer was completed on Algorand</p>
 * @param client AlgodV2 client
 * @param appId Most likely the Token bridge ID
 * @param signedVAA VAA to check
 * @param wallet The account paying the bill for this (it isn't free)
 * @returns true if VAA has been redeemed, false otherwise
 */
function getIsTransferCompletedAlgorand(client, appId, signedVAA) {
    return __awaiter(this, void 0, void 0, function* () {
        const parsedVAA = algorand_1._parseVAAAlgorand(signedVAA);
        const seq = parsedVAA.sequence;
        const chainRaw = parsedVAA.chainRaw; // this needs to be a hex string
        const em = parsedVAA.emitter; // this needs to be a hex string
        const { doesExist, lsa } = yield algorand_1.calcLogicSigAccount(client, appId, seq / BigInt(algorand_1.MAX_BITS), chainRaw + em);
        if (!doesExist) {
            return false;
        }
        const seqAddr = lsa.address();
        const retVal = yield checkBitsSet(client, appId, seqAddr, seq);
        return retVal;
    });
}
exports.getIsTransferCompletedAlgorand = getIsTransferCompletedAlgorand;
