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 { bigIntToBytes } from "algosdk";
import axios from "axios";
import { fromUint8Array } from "js-base64";
import { redeemOnTerra } from ".";
import { TERRA_REDEEMED_CHECK_WALLET_ADDRESS, ensureHexPrefix } from "..";
import { BITS_PER_KEY, MAX_BITS, _parseVAAAlgorand, calcLogicSigAccount, } from "../algorand";
import { getSignedVAAHash } from "../bridge";
import { Bridge__factory } from "../ethers-contracts";
import { getClaim } from "../solana/wormhole";
import { getObjectFields, getTableKeyType } from "../sui/utils";
import { safeBigIntToNumber } from "../utils/bigint";
import { callFunctionNear } from "../utils/near";
import { parseVaa } from "../vaa/wormhole";
export function getIsTransferCompletedEth(tokenBridgeAddress, provider, signedVAA) {
    return __awaiter(this, void 0, void 0, function* () {
        const tokenBridge = Bridge__factory.connect(tokenBridgeAddress, provider);
        const signedVAAHash = getSignedVAAHash(signedVAA);
        return yield tokenBridge.isTransferCompleted(signedVAAHash);
    });
}
// Note: this function is the legacy implementation for terra classic.  New
// cosmwasm sdk functions should instead be based on
// `getIsTransferCompletedTerra2`.
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
            if (e.response.data.message.includes("VaaAlreadyExecuted")) {
                return true;
            }
            else {
                throw e;
            }
        }
        return false;
    });
}
/**
 * This function is used to check if a VAA has been redeemed on terra2 by
 * querying the token bridge contract.
 * @param tokenBridgeAddress The token bridge address (bech32)
 * @param signedVAA The signed VAA byte array
 * @param client The LCD client. Only used for querying, not transactions will
 * be signed
 */
export function getIsTransferCompletedTerra2(tokenBridgeAddress, signedVAA, client) {
    return __awaiter(this, void 0, void 0, function* () {
        const result = yield client.wasm.contractQuery(tokenBridgeAddress, {
            is_vaa_redeemed: {
                vaa: fromUint8Array(signedVAA),
            },
        });
        return result.is_redeemed;
    });
}
export function getIsTransferCompletedXpla(tokenBridgeAddress, signedVAA, client) {
    return __awaiter(this, void 0, void 0, function* () {
        const result = yield client.wasm.contractQuery(tokenBridgeAddress, {
            is_vaa_redeemed: {
                vaa: fromUint8Array(signedVAA),
            },
        });
        return result.is_redeemed;
    });
}
export function getIsTransferCompletedSolana(tokenBridgeAddress, signedVAA, connection, commitment) {
    return __awaiter(this, void 0, void 0, function* () {
        const parsed = parseVaa(signedVAA);
        return getClaim(connection, tokenBridgeAddress, parsed.emitterAddress, parsed.emitterChain, parsed.sequence, commitment).catch((e) => false);
    });
}
// 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
 * @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;
    });
}
export function getIsTransferCompletedNear(provider, tokenBridge, signedVAA) {
    return __awaiter(this, void 0, void 0, function* () {
        const vaa = Buffer.from(signedVAA).toString("hex");
        return (yield callFunctionNear(provider, tokenBridge, "is_transfer_completed", {
            vaa,
        }))[1];
    });
}
/**
 * Determine whether or not the transfer in the given VAA has completed on Aptos.
 * @param client Client used to transfer data to/from Aptos node
 * @param tokenBridgeAddress Address of token bridge
 * @param transferVAA Bytes of transfer VAA
 * @returns True if transfer is completed
 */
export function getIsTransferCompletedAptos(client, tokenBridgeAddress, transferVAA) {
    return __awaiter(this, void 0, void 0, function* () {
        // get handle
        tokenBridgeAddress = ensureHexPrefix(tokenBridgeAddress);
        const state = (yield client.getAccountResource(tokenBridgeAddress, `${tokenBridgeAddress}::state::State`)).data;
        const handle = state.consumed_vaas.elems.handle;
        // check if vaa hash is in consumed_vaas
        const transferVAAHash = getSignedVAAHash(transferVAA);
        try {
            // when accessing Set<T>, key is type T and value is 0
            yield client.getTableItem(handle, {
                key_type: "vector<u8>",
                value_type: "u8",
                key: transferVAAHash,
            });
            return true;
        }
        catch (_a) {
            return false;
        }
    });
}
export function getIsTransferCompletedSui(provider, tokenBridgeStateObjectId, transferVAA) {
    var _a, _b, _c, _d, _e, _f, _g, _h;
    return __awaiter(this, void 0, void 0, function* () {
        const tokenBridgeStateFields = yield getObjectFields(provider, tokenBridgeStateObjectId);
        if (!tokenBridgeStateFields) {
            throw new Error("Unable to fetch object fields from token bridge state");
        }
        const hashes = (_b = (_a = tokenBridgeStateFields.consumed_vaas) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b.hashes;
        const tableObjectId = (_f = (_e = (_d = (_c = hashes === null || hashes === void 0 ? void 0 : hashes.fields) === null || _c === void 0 ? void 0 : _c.items) === null || _d === void 0 ? void 0 : _d.fields) === null || _e === void 0 ? void 0 : _e.id) === null || _f === void 0 ? void 0 : _f.id;
        if (!tableObjectId) {
            throw new Error("Unable to fetch consumed VAAs table");
        }
        const keyType = getTableKeyType((_h = (_g = hashes === null || hashes === void 0 ? void 0 : hashes.fields) === null || _g === void 0 ? void 0 : _g.items) === null || _h === void 0 ? void 0 : _h.type);
        if (!keyType) {
            throw new Error("Unable to get key type");
        }
        const hash = getSignedVAAHash(transferVAA);
        const response = yield provider.getDynamicFieldObject({
            parentId: tableObjectId,
            name: {
                type: keyType,
                value: {
                    data: [...Buffer.from(hash.slice(2), "hex")],
                },
            },
        });
        if (!response.error) {
            return true;
        }
        if (response.error.code === "dynamicFieldNotFound") {
            return false;
        }
        throw new Error(`Unexpected getDynamicFieldObject response ${response.error}`);
    });
}
