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());
    });
};
import { PublicKey } from "@solana/web3.js";
import { bigIntToBytes } from "algosdk";
import axios from "axios";
import { redeemOnTerra } from ".";
import { TERRA_REDEEMED_CHECK_WALLET_ADDRESS } from "..";
import { extractSequenceFromVAA } from "../utils";
import { addressFromContractId, subContractId } from "@alephium/web3";
import { BITS_PER_KEY, calcLogicSigAccount, MAX_BITS, _parseVAAAlgorand, } from "../algorand";
import { getSignedVAAHash } from "../bridge";
import { Bridge__factory } from "../ethers-contracts";
import { importCoreWasm } from "../solana/wasm";
import { safeBigIntToNumber } from "../utils/bigint";
import { zeroPad } from "./alephium";
import { TokenBridgeForChain } from "../alephium-contracts/ts/TokenBridgeForChain";
import { UnexecutedSequence } from "../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 = zeroPad(Math.floor(Number(sequence) / 256).toString(16), 8);
        const contractId = subContractId(tokenBridgeForChainId, path, groupIndex);
        const contractAddress = addressFromContractId(contractId);
        try {
            const contract = 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;
        }
    });
}
export function getIsTransferCompletedAlph(tokenBridgeForChainId, groupIndex, signedVAA) {
    return __awaiter(this, void 0, void 0, function* () {
        const tokenBridgeForChainAddress = addressFromContractId(tokenBridgeForChainId);
        const contract = TokenBridgeForChain.at(tokenBridgeForChainAddress);
        const contractState = yield contract.fetchState();
        const sequence = 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;
    });
}
export function getIsTransferCompletedEth(tokenBridgeAddress, provider, signedVAA) {
    return __awaiter(this, void 0, void 0, function* () {
        const tokenBridge = Bridge__factory.connect(tokenBridgeAddress, provider);
        const signedVAAHash = yield getSignedVAAHash(signedVAA);
        return yield tokenBridge.isTransferCompleted(signedVAAHash);
    });
}
export function getIsTransferCompletedTerra(tokenBridgeAddress, signedVAA, client, gasPriceUrl) {
    return __awaiter(this, void 0, void 0, function* () {
        const msg = yield redeemOnTerra(tokenBridgeAddress, TERRA_REDEEMED_CHECK_WALLET_ADDRESS, signedVAA);
        // TODO: remove gasPriceUrl and just use the client's gas prices
        const gasPrices = yield axios.get(gasPriceUrl).then((result) => result.data);
        const account = yield client.auth.accountInfo(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;
    });
}
export function getIsTransferCompletedSolana(tokenBridgeAddress, signedVAA, connection) {
    return __awaiter(this, void 0, void 0, function* () {
        const { claim_address } = yield importCoreWasm();
        const claimAddress = yield claim_address(tokenBridgeAddress, signedVAA);
        const claimInfo = yield connection.getAccountInfo(new PublicKey(claimAddress), "confirmed");
        return !!claimInfo;
    });
}
// 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(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 = safeBigIntToNumber(seq - start);
        // s should be in the range [0..15]
        const s = Math.floor(beg / BITS_PER_KEY);
        const b = Math.floor((beg - s * BITS_PER_KEY) / 8);
        const key = Buffer.from(bigIntToBytes(s, 1)).toString("base64");
        appState.forEach((kv) => {
            if (kv["key"] === key) {
                const v = Buffer.from(kv["value"]["bytes"], "base64");
                const bt = 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
 */
export function getIsTransferCompletedAlgorand(client, appId, signedVAA) {
    return __awaiter(this, void 0, void 0, function* () {
        const parsedVAA = _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 calcLogicSigAccount(client, appId, seq / BigInt(MAX_BITS), chainRaw + em);
        if (!doesExist) {
            return false;
        }
        const seqAddr = lsa.address();
        const retVal = yield checkBitsSet(client, appId, seqAddr, seq);
        return retVal;
    });
}
