"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.postVaa = exports.createPostVaaInstruction = exports.createVerifySignaturesInstructions = exports.postVaaWithRetry = void 0;
const web3_js_1 = require("@solana/web3.js");
const __1 = require("..");
const solana_1 = require("../utils/solana");
const rust_1 = require("./rust");
const wasm_1 = require("./wasm");
function postVaaWithRetry(connection, signTransaction, bridge_id, payer, vaa, maxRetries) {
    return __awaiter(this, void 0, void 0, function* () {
        const unsignedTransactions = [];
        const signature_set = web3_js_1.Keypair.generate();
        const instructions = yield createVerifySignaturesInstructions(connection, bridge_id, payer, vaa, signature_set);
        const finalInstruction = yield createPostVaaInstruction(bridge_id, payer, vaa, signature_set);
        if (!finalInstruction) {
            return Promise.reject("Failed to construct the transaction.");
        }
        //The verify signatures instructions can be batched into groups of 2 safely,
        //reducing the total number of transactions.
        const batchableChunks = __1.chunks(instructions, 2);
        batchableChunks.forEach((chunk) => {
            let transaction;
            if (chunk.length === 1) {
                transaction = new web3_js_1.Transaction().add(chunk[0]);
            }
            else {
                transaction = new web3_js_1.Transaction().add(chunk[0], chunk[1]);
            }
            unsignedTransactions.push(transaction);
        });
        //the postVaa instruction can only execute after the verifySignature transactions have
        //successfully completed.
        const finalTransaction = new web3_js_1.Transaction().add(finalInstruction);
        //The signature_set keypair also needs to sign the verifySignature transactions, thus a wrapper is needed.
        const partialSignWrapper = (transaction) => {
            transaction.partialSign(signature_set);
            return signTransaction(transaction);
        };
        yield solana_1.sendAndConfirmTransactionsWithRetry(connection, partialSignWrapper, payer, unsignedTransactions, maxRetries);
        //While the signature_set is used to create the final instruction, it doesn't need to sign it.
        yield solana_1.sendAndConfirmTransactionsWithRetry(connection, signTransaction, payer, [finalTransaction], maxRetries);
        return Promise.resolve();
    });
}
exports.postVaaWithRetry = postVaaWithRetry;
/*
This returns an array of instructions required to verify the signatures of a VAA, and upload it to the blockchain.
signature_set should be a new keypair, and also needs to partial sign the transaction when these instructions are submitted.
*/
function createVerifySignaturesInstructions(connection, bridge_id, payer, vaa, signature_set) {
    return __awaiter(this, void 0, void 0, function* () {
        const output = [];
        const { guardian_set_address, parse_guardian_set, parse_vaa, verify_signatures_ix, } = yield wasm_1.importCoreWasm();
        const { guardian_set_index } = parse_vaa(new Uint8Array(vaa));
        let guardian_addr = new web3_js_1.PublicKey(guardian_set_address(bridge_id, guardian_set_index));
        let acc = yield connection.getAccountInfo(guardian_addr);
        if ((acc === null || acc === void 0 ? void 0 : acc.data) === undefined) {
            return output;
        }
        let guardian_data = parse_guardian_set(new Uint8Array(acc === null || acc === void 0 ? void 0 : acc.data));
        let txs = verify_signatures_ix(bridge_id, payer, guardian_set_index, guardian_data, signature_set.publicKey.toString(), vaa);
        // Add transfer instruction to transaction
        for (let tx of txs) {
            let ixs = tx.map((v) => {
                return rust_1.ixFromRust(v);
            });
            output.push(ixs[0], ixs[1]);
        }
        return output;
    });
}
exports.createVerifySignaturesInstructions = createVerifySignaturesInstructions;
/*
This will return the postVaaInstruction. This should only be executed after the verifySignaturesInstructions have been executed.
signatureSetKeypair should be the same keypair used for verifySignaturesInstructions, but does not need to partialSign the transaction
when this instruction is submitted.
*/
function createPostVaaInstruction(bridge_id, payer, vaa, signatureSetKeypair) {
    return __awaiter(this, void 0, void 0, function* () {
        const { post_vaa_ix } = yield wasm_1.importCoreWasm();
        return rust_1.ixFromRust(post_vaa_ix(bridge_id, payer, signatureSetKeypair.publicKey.toString(), vaa));
    });
}
exports.createPostVaaInstruction = createPostVaaInstruction;
/*
  @deprecated
  Instead, either use postVaaWithRetry or create, sign, and send the verifySignaturesInstructions & postVaaInstruction yourself.
  
  This function is equivalent to a postVaaWithRetry with a maxRetries of 0.
*/
function postVaa(connection, signTransaction, bridge_id, payer, vaa) {
    return __awaiter(this, void 0, void 0, function* () {
        const { guardian_set_address, parse_guardian_set, parse_vaa, post_vaa_ix, verify_signatures_ix, } = yield wasm_1.importCoreWasm();
        const { guardian_set_index } = parse_vaa(new Uint8Array(vaa));
        let guardian_addr = new web3_js_1.PublicKey(guardian_set_address(bridge_id, guardian_set_index));
        let acc = yield connection.getAccountInfo(guardian_addr);
        if ((acc === null || acc === void 0 ? void 0 : acc.data) === undefined) {
            return;
        }
        let guardian_data = parse_guardian_set(new Uint8Array(acc === null || acc === void 0 ? void 0 : acc.data));
        let signature_set = web3_js_1.Keypair.generate();
        let txs = verify_signatures_ix(bridge_id, payer, guardian_set_index, guardian_data, signature_set.publicKey.toString(), vaa);
        // Add transfer instruction to transaction
        for (let tx of txs) {
            let ixs = tx.map((v) => {
                return rust_1.ixFromRust(v);
            });
            let transaction = new web3_js_1.Transaction().add(...ixs);
            const { blockhash } = yield connection.getRecentBlockhash();
            transaction.recentBlockhash = blockhash;
            transaction.feePayer = new web3_js_1.PublicKey(payer);
            transaction.partialSign(signature_set);
            // Sign transaction, broadcast, and confirm
            const signed = yield signTransaction(transaction);
            const txid = yield connection.sendRawTransaction(signed.serialize());
            yield connection.confirmTransaction(txid);
        }
        let ix = rust_1.ixFromRust(post_vaa_ix(bridge_id, payer, signature_set.publicKey.toString(), vaa));
        let transaction = new web3_js_1.Transaction().add(ix);
        const { blockhash } = yield connection.getRecentBlockhash();
        transaction.recentBlockhash = blockhash;
        transaction.feePayer = new web3_js_1.PublicKey(payer);
        const signed = yield signTransaction(transaction);
        const txid = yield connection.sendRawTransaction(signed.serialize());
        yield connection.confirmTransaction(txid);
    });
}
exports.postVaa = postVaa;
