"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const anchor = __importStar(require("@coral-xyz/anchor"));
const anchor_1 = require("@coral-xyz/anchor");
const spl_token_1 = require("@solana/spl-token");
const web3_js_1 = require("@solana/web3.js");
const SvmUtils_1 = require("../../src/SvmUtils");
const SvmSpoke_common_1 = require("./SvmSpoke.common");
const { provider, connection, program, owner, chainId, seedBalance } = SvmSpoke_common_1.common;
const { recipient, initializeState, setCurrentTime, assertSE, assert } = SvmSpoke_common_1.common;
describe("svm_spoke.fill", () => {
    anchor.setProvider(provider);
    const payer = anchor.AnchorProvider.env().wallet.payer;
    const relayer = web3_js_1.Keypair.generate();
    const otherRelayer = web3_js_1.Keypair.generate();
    let state, mint, relayerTA, recipientTA, otherRelayerTA;
    const relayAmount = 500000;
    let relayData; // reused relay data for all tests.
    let accounts; // Store accounts to simplify contract interactions.
    function updateRelayData(newRelayData) {
        relayData = newRelayData;
        const relayHashUint8Array = (0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId);
        const [fillStatusPDA] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fills"), relayHashUint8Array], program.programId);
        accounts = {
            state,
            signer: relayer.publicKey,
            mintAccount: mint,
            relayerTokenAccount: relayerTA,
            recipientTokenAccount: recipientTA,
            fillStatus: fillStatusPDA,
            tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
            associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
            systemProgram: anchor.web3.SystemProgram.programId,
        };
    }
    before("Creates token mint and associated token accounts", async () => {
        mint = await (0, spl_token_1.createMint)(connection, payer, owner, owner, 6);
        recipientTA = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(connection, payer, mint, recipient)).address;
        relayerTA = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(connection, payer, mint, relayer.publicKey)).address;
        otherRelayerTA = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(connection, payer, mint, otherRelayer.publicKey)).address;
        await (0, spl_token_1.mintTo)(connection, payer, mint, relayerTA, owner, seedBalance);
        await (0, spl_token_1.mintTo)(connection, payer, mint, otherRelayerTA, owner, seedBalance);
        await connection.requestAirdrop(relayer.publicKey, 10_000_000_000); // 10 SOL
        await connection.requestAirdrop(otherRelayer.publicKey, 10_000_000_000); // 10 SOL
    });
    beforeEach(async () => {
        state = await initializeState();
        const initialRelayData = {
            depositor: recipient,
            recipient: recipient,
            exclusiveRelayer: relayer.publicKey,
            inputToken: mint, // This is lazy. it should be an encoded token from a separate domain most likely.
            outputToken: mint,
            inputAmount: new anchor_1.BN(relayAmount),
            outputAmount: new anchor_1.BN(relayAmount),
            originChainId: new anchor_1.BN(1),
            depositId: new anchor_1.BN(Math.floor(Math.random() * 1000000)), // force that we always have a new deposit id.
            fillDeadline: new anchor_1.BN(Math.floor(Date.now() / 1000) + 60), // 1 minute from now
            exclusivityDeadline: new anchor_1.BN(Math.floor(Date.now() / 1000) + 30), // 30 seconds from now
            message: Buffer.from("Test message"),
        };
        updateRelayData(initialRelayData);
    });
    it("Fills a V3 relay and verifies balances", async () => {
        // Verify recipient's balance before the fill
        let recipientAccount = await (0, spl_token_1.getAccount)(connection, recipientTA);
        assertSE(recipientAccount.amount, "0", "Recipient's balance should be 0 before the fill");
        // Verify relayer's balance before the fill
        let relayerAccount = await (0, spl_token_1.getAccount)(connection, relayerTA);
        assertSE(relayerAccount.amount, seedBalance, "Relayer's balance should be equal to seed balance before the fill");
        const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
        await program.methods
            .fillV3Relay(relayHash, relayData, new anchor_1.BN(1), relayer.publicKey)
            .accounts(accounts)
            .signers([relayer])
            .rpc();
        // Verify relayer's balance after the fill
        relayerAccount = await (0, spl_token_1.getAccount)(connection, relayerTA);
        assertSE(relayerAccount.amount, seedBalance - relayAmount, "Relayer's balance should be reduced by the relay amount");
        // Verify recipient's balance after the fill
        recipientAccount = await (0, spl_token_1.getAccount)(connection, recipientTA);
        assertSE(recipientAccount.amount, relayAmount, "Recipient's balance should be increased by the relay amount");
    });
    it("Verifies FilledV3Relay event after filling a relay", async () => {
        const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
        await program.methods
            .fillV3Relay(relayHash, relayData, new anchor_1.BN(420), otherRelayer.publicKey)
            .accounts(accounts)
            .signers([relayer])
            .rpc();
        // Fetch and verify the FilledV3Relay event
        await new Promise((resolve) => setTimeout(resolve, 500));
        const events = await (0, SvmUtils_1.readProgramEvents)(connection, program);
        const event = events.find((event) => event.name === "filledV3Relay").data;
        assert.isNotNull(event, "FilledV3Relay event should be emitted");
        // Verify that the event data matches the relay data.
        Object.keys(relayData).forEach((key) => {
            assertSE(event[key], relayData[key], `${key.charAt(0).toUpperCase() + key.slice(1)} should match`);
        });
        // These props below are not part of relayData.
        assertSE(event.repaymentChainId, new anchor_1.BN(420), "Repayment chain id should match");
        assertSE(event.relayer, otherRelayer.publicKey, "Repayment address should match");
    });
    it("Fails to fill a V3 relay after the fill deadline", async () => {
        updateRelayData({ ...relayData, fillDeadline: new anchor_1.BN(Math.floor(Date.now() / 1000) - 69) }); // 69 seconds ago
        const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
        try {
            await program.methods
                .fillV3Relay(relayHash, relayData, new anchor_1.BN(1), relayer.publicKey)
                .accounts(accounts)
                .signers([relayer])
                .rpc();
            assert.fail("Fill should have failed due to fill deadline passed");
        }
        catch (err) {
            assert.include(err.toString(), "ExpiredFillDeadline", "Expected ExpiredFillDeadline error");
        }
    });
    it("Fails to fill a V3 relay by non-exclusive relayer before exclusivity deadline", async () => {
        accounts.signer = otherRelayer.publicKey;
        accounts.relayerTokenAccount = otherRelayerTA;
        const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
        try {
            await program.methods
                .fillV3Relay(relayHash, relayData, new anchor_1.BN(1), relayer.publicKey)
                .accounts(accounts)
                .signers([otherRelayer])
                .rpc();
            assert.fail("Fill should have failed due to non-exclusive relayer before exclusivity deadline");
        }
        catch (err) {
            assert.include(err.toString(), "NotExclusiveRelayer", "Expected NotExclusiveRelayer error");
        }
    });
    it("Allows fill by non-exclusive relayer after exclusivity deadline", async () => {
        updateRelayData({ ...relayData, exclusivityDeadline: new anchor_1.BN(Math.floor(Date.now() / 1000) - 100) });
        accounts.signer = otherRelayer.publicKey;
        accounts.relayerTokenAccount = otherRelayerTA;
        const recipientAccountBefore = await (0, spl_token_1.getAccount)(connection, recipientTA);
        const relayerAccountBefore = await (0, spl_token_1.getAccount)(connection, otherRelayerTA);
        const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
        await program.methods
            .fillV3Relay(relayHash, relayData, new anchor_1.BN(1), relayer.publicKey)
            .accounts(accounts)
            .signers([otherRelayer])
            .rpc();
        // Verify relayer's balance after the fill
        const relayerAccountAfter = await (0, spl_token_1.getAccount)(connection, otherRelayerTA);
        assertSE(relayerAccountAfter.amount, BigInt(relayerAccountBefore.amount) - BigInt(relayAmount), "Relayer's balance should be reduced by the relay amount");
        // Verify recipient's balance after the fill
        const recipientAccountAfter = await (0, spl_token_1.getAccount)(connection, recipientTA);
        assertSE(recipientAccountAfter.amount, BigInt(recipientAccountBefore.amount) + BigInt(relayAmount), "Recipient's balance should be increased by the relay amount");
    });
    it("Fails to fill a V3 relay with the same deposit data multiple times", async () => {
        const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
        // First fill attempt
        await program.methods
            .fillV3Relay(relayHash, relayData, new anchor_1.BN(1), relayer.publicKey)
            .accounts(accounts)
            .signers([relayer])
            .rpc();
        // Second fill attempt with the same data
        try {
            await program.methods
                .fillV3Relay(relayHash, relayData, new anchor_1.BN(1), relayer.publicKey)
                .accounts(accounts)
                .signers([relayer])
                .rpc();
            assert.fail("Fill should have failed due to RelayFilled error");
        }
        catch (err) {
            assert.include(err.toString(), "RelayFilled", "Expected RelayFilled error");
        }
    });
    it("Closes the fill PDA after the fill", async () => {
        const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
        const closeFillPdaAccounts = {
            state,
            signer: relayer.publicKey,
            fillStatus: accounts.fillStatus,
            systemProgram: anchor.web3.SystemProgram.programId,
        };
        // Execute the fill_v3_relay call
        await program.methods
            .fillV3Relay(relayHash, relayData, new anchor_1.BN(1), relayer.publicKey)
            .accounts(accounts)
            .signers([relayer])
            .rpc();
        // Verify the fill PDA exists before closing
        const fillStatusAccountBefore = await connection.getAccountInfo(accounts.fillStatus);
        assert.isNotNull(fillStatusAccountBefore, "Fill PDA should exist before closing");
        // Attempt to close the fill PDA before the fill deadline should fail.
        try {
            await program.methods.closeFillPda(relayHash, relayData).accounts(closeFillPdaAccounts).signers([relayer]).rpc();
            assert.fail("Closing fill PDA should have failed before fill deadline");
        }
        catch (err) {
            assert.include(err.toString(), "CanOnlyCloseFillStatusPdaIfFillDeadlinePassed", "Expected CanOnlyCloseFillStatusPdaIfFillDeadlinePassed error");
        }
        // Set the current time to past the fill deadline
        await setCurrentTime(program, state, relayer, relayData.fillDeadline.add(new anchor_1.BN(1), relayer.publicKey));
        // Close the fill PDA
        await program.methods.closeFillPda(relayHash, relayData).accounts(closeFillPdaAccounts).signers([relayer]).rpc();
        // Verify the fill PDA is closed
        const fillStatusAccountAfter = await connection.getAccountInfo(accounts.fillStatus);
        assert.isNull(fillStatusAccountAfter, "Fill PDA should be closed after closing");
    });
    it("Fetches FillStatusAccount before and after fillV3Relay", async () => {
        const relayHash = (0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId);
        const [fillStatusPDA] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fills"), relayHash], program.programId);
        // Fetch FillStatusAccount before fillV3Relay
        let fillStatusAccount = await program.account.fillStatusAccount.fetchNullable(fillStatusPDA);
        assert.isNull(fillStatusAccount, "FillStatusAccount should be uninitialized before fillV3Relay");
        // Fill the relay
        await program.methods
            .fillV3Relay(Array.from(relayHash), relayData, new anchor_1.BN(1), relayer.publicKey)
            .accounts(accounts)
            .signers([relayer])
            .rpc();
        // Fetch FillStatusAccount after fillV3Relay
        fillStatusAccount = await program.account.fillStatusAccount.fetch(fillStatusPDA);
        assert.isNotNull(fillStatusAccount, "FillStatusAccount should be initialized after fillV3Relay");
        assert.equal(JSON.stringify(fillStatusAccount.status), `{\"filled\":{}}`, "FillStatus should be Filled");
        assert.equal(fillStatusAccount.relayer.toString(), relayer.publicKey.toString(), "Caller should be set as relayer");
    });
    it("Fails to fill a relay when fills are paused", async () => {
        // Pause fills
        const pauseFillsAccounts = { state: state, signer: owner, program: program.programId };
        await program.methods.pauseFills(true).accounts(pauseFillsAccounts).rpc();
        const stateAccountData = await program.account.state.fetch(state);
        assert.isTrue(stateAccountData.pausedFills, "Fills should be paused");
        // Try to fill the relay. This should fail because fills are paused.
        const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
        try {
            await program.methods
                .fillV3Relay(relayHash, relayData, new anchor_1.BN(1), relayer.publicKey)
                .accounts(accounts)
                .signers([relayer])
                .rpc();
            assert.fail("Should not be able to fill relay when fills are paused");
        }
        catch (err) {
            assert.include(err.toString(), "Fills are currently paused!", "Expected fills paused error");
        }
    });
    it("Fails to fill a relay to wrong recipient token account", async () => {
        const relayHash = (0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId);
        // Create new accounts as derived from wrong recipient.
        const wrongRecipient = web3_js_1.Keypair.generate().publicKey;
        const wrongRecipientTA = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(connection, payer, mint, wrongRecipient)).address;
        const [wrongFillStatus] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fills"), relayHash], program.programId);
        try {
            await program.methods
                .fillV3Relay(Array.from(relayHash), relayData, new anchor_1.BN(1), relayer.publicKey)
                .accounts({
                ...accounts,
                recipientTokenAccount: wrongRecipientTA,
                fillStatus: wrongFillStatus,
            })
                .signers([relayer])
                .rpc();
            assert.fail("Should not be able to fill relay to wrong recipient token account");
        }
        catch (err) {
            assert.instanceOf(err, anchor.AnchorError);
            assert.strictEqual(err.error.errorCode.code, "ConstraintTokenOwner", "Expected error code ConstraintTokenOwner");
        }
    });
    it("Fails to fill a relay for mint inconsistent output_token", async () => {
        const relayHash = (0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId);
        // Create and fund new accounts as derived from wrong mint account.
        const wrongMint = await (0, spl_token_1.createMint)(connection, payer, owner, owner, 6);
        const wrongRecipientTA = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(connection, payer, wrongMint, recipient)).address;
        const wrongRelayerTA = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(connection, payer, wrongMint, relayer.publicKey))
            .address;
        await (0, spl_token_1.mintTo)(connection, payer, wrongMint, wrongRelayerTA, owner, seedBalance);
        try {
            await program.methods
                .fillV3Relay(Array.from(relayHash), relayData, new anchor_1.BN(1), relayer.publicKey)
                .accounts({
                ...accounts,
                mintAccount: wrongMint,
                relayerTokenAccount: wrongRelayerTA,
                recipientTokenAccount: wrongRecipientTA,
            })
                .signers([relayer])
                .rpc();
            assert.fail("Should not be able to process fill for inconsistent mint");
        }
        catch (err) {
            assert.instanceOf(err, anchor.AnchorError);
            assert.strictEqual(err.error.errorCode.code, "InvalidMint", "Expected error code InvalidMint");
        }
    });
    it("Self-relay does not invoke token transfer", async () => {
        // Set recipient to be the same as relayer.
        updateRelayData({ ...relayData, depositor: relayer.publicKey, recipient: relayer.publicKey });
        accounts.recipientTokenAccount = relayerTA;
        // Store relayer's balance before the fill
        const iRelayerBalance = (await (0, spl_token_1.getAccount)(connection, relayerTA)).amount;
        const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
        const txSignature = await program.methods
            .fillV3Relay(relayHash, relayData, new anchor_1.BN(1), relayer.publicKey)
            .accounts(accounts)
            .signers([relayer])
            .rpc();
        // Verify relayer's balance after the fill is unchanged
        const fRelayerBalance = (await (0, spl_token_1.getAccount)(connection, relayerTA)).amount;
        assertSE(fRelayerBalance, iRelayerBalance, "Relayer's balance should not have changed");
        await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for tx processing
        const txResult = await connection.getTransaction(txSignature, {
            commitment: "confirmed",
            maxSupportedTransactionVersion: 0,
        });
        if (txResult === null || txResult.meta === null)
            throw new Error("Transaction meta not confirmed");
        if (txResult.meta.logMessages === null || txResult.meta.logMessages === undefined)
            throw new Error("Transaction logs not found");
        assert.isTrue(txResult.meta.logMessages.every((log) => !log.includes(`Program ${spl_token_1.TOKEN_PROGRAM_ID} invoke`)), "Token Program should not be invoked");
    });
    it("Fills a V3 relay from custom relayer token account", async () => {
        // Create and mint to custom relayer token account
        const customKeypair = web3_js_1.Keypair.generate();
        const customRelayerTA = await (0, spl_token_1.createAccount)(connection, payer, mint, relayer.publicKey, customKeypair);
        await (0, spl_token_1.mintTo)(connection, payer, mint, customRelayerTA, owner, seedBalance);
        // Save balances before the the fill
        const iRelayerBal = (await (0, spl_token_1.getAccount)(connection, customRelayerTA)).amount;
        const iRecipientBal = (await (0, spl_token_1.getAccount)(connection, recipientTA)).amount;
        // Fill relay from custom relayer token account
        accounts.relayerTokenAccount = customRelayerTA;
        const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(relayData, chainId));
        await program.methods
            .fillV3Relay(relayHash, relayData, new anchor_1.BN(1), relayer.publicKey)
            .accounts(accounts)
            .signers([relayer])
            .rpc();
        // Verify balances after the fill
        const fRelayerBal = (await (0, spl_token_1.getAccount)(connection, customRelayerTA)).amount;
        const fRecipientBal = (await (0, spl_token_1.getAccount)(connection, recipientTA)).amount;
        assertSE(fRelayerBal, iRelayerBal - BigInt(relayAmount), "Relayer's balance should be reduced by the relay amount");
        assertSE(fRecipientBal, iRecipientBal + BigInt(relayAmount), "Recipient's balance should be increased by the relay amount");
    });
    it("Fills a deposit for a recipient without an existing ATA", async () => {
        // Generate a new recipient account
        const newRecipient = web3_js_1.Keypair.generate().publicKey;
        const newRecipientATA = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, newRecipient);
        // Attempt to fill a deposit, expecting failure due to missing ATA
        const newRelayData = {
            ...relayData,
            recipient: newRecipient,
            depositId: new anchor_1.BN(Math.floor(Math.random() * 1000000)),
        };
        updateRelayData(newRelayData);
        accounts.recipientTokenAccount = newRecipientATA;
        const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(newRelayData, chainId));
        try {
            await program.methods
                .fillV3Relay(relayHash, newRelayData, new anchor_1.BN(1), relayer.publicKey)
                .accounts(accounts)
                .signers([relayer])
                .rpc();
            assert.fail("Fill should have failed due to missing ATA");
        }
        catch (err) {
            assert.include(err.toString(), "AccountNotInitialized", "Expected AccountNotInitialized error");
        }
        // Create the ATA using the create_token_accounts method
        const createTokenAccountsInstruction = await program.methods
            .createTokenAccounts()
            .accounts({ signer: relayer.publicKey, mint, tokenProgram: spl_token_1.TOKEN_PROGRAM_ID })
            .remainingAccounts([
            { pubkey: newRecipient, isWritable: false, isSigner: false },
            { pubkey: newRecipientATA, isWritable: true, isSigner: false },
        ])
            .instruction();
        // Fill the deposit in the same transaction
        const fillInstruction = await program.methods
            .fillV3Relay(relayHash, newRelayData, new anchor_1.BN(1), relayer.publicKey)
            .accounts(accounts)
            .instruction();
        // Create and send the transaction
        const transaction = new anchor_1.web3.Transaction().add(createTokenAccountsInstruction, fillInstruction);
        await anchor_1.web3.sendAndConfirmTransaction(connection, transaction, [relayer]);
        // Verify the recipient's balance after the fill
        const recipientAccount = await (0, spl_token_1.getAccount)(connection, newRecipientATA);
        assertSE(recipientAccount.amount, relayAmount, "Recipient's balance should be increased by the relay amount");
    });
    it("Max fills in one transaction with account creation", async () => {
        // Save relayer balance before the the fills
        const iRelayerBal = (await (0, spl_token_1.getAccount)(connection, relayerTA)).amount;
        // Larger number of fills would exceed the transaction size limit.
        const numberOfFills = 2;
        // Build instruction for all recipient ATA creation
        const recipientAuthorities = Array.from({ length: numberOfFills }, () => web3_js_1.Keypair.generate().publicKey);
        const recipientAssociatedTokens = recipientAuthorities.map((authority) => (0, spl_token_1.getAssociatedTokenAddressSync)(mint, authority));
        const remainingAccounts = recipientAuthorities.flatMap((authority, index) => [
            { pubkey: authority, isWritable: false, isSigner: false },
            { pubkey: recipientAssociatedTokens[index], isWritable: true, isSigner: false },
        ]);
        const createTokenAccountsInstruction = await program.methods
            .createTokenAccounts()
            .accounts({ signer: relayer.publicKey, mint, tokenProgram: spl_token_1.TOKEN_PROGRAM_ID })
            .remainingAccounts(remainingAccounts)
            .instruction();
        // Build instructions for all fills
        const fillInstructions = [];
        for (let i = 0; i < numberOfFills; i++) {
            const newRelayData = {
                ...relayData,
                recipient: recipientAuthorities[i],
                depositId: new anchor_1.BN(Math.floor(Math.random() * 1000000)),
            };
            updateRelayData(newRelayData);
            accounts.recipientTokenAccount = recipientAssociatedTokens[i];
            const relayHash = Array.from((0, SvmUtils_1.calculateRelayHashUint8Array)(newRelayData, chainId));
            const fillInstruction = await program.methods
                .fillV3Relay(relayHash, newRelayData, new anchor_1.BN(1), relayer.publicKey)
                .accounts(accounts)
                .instruction();
            fillInstructions.push(fillInstruction);
        }
        // Consolidate all above addresses into a single array for the  Address Lookup Table (ALT).
        const lookupAddresses = [...Object.values(accounts), ...recipientAuthorities, ...recipientAssociatedTokens];
        // Create instructions for creating and extending the ALT.
        const [lookupTableInstruction, lookupTableAddress] = await web3_js_1.AddressLookupTableProgram.createLookupTable({
            authority: relayer.publicKey,
            payer: relayer.publicKey,
            recentSlot: await connection.getSlot(),
        });
        // Submit the ALT creation transaction
        await anchor_1.web3.sendAndConfirmTransaction(connection, new anchor_1.web3.Transaction().add(lookupTableInstruction), [relayer], {
            skipPreflight: true, // Avoids recent slot mismatch in simulation.
        });
        // Extend the ALT with all accounts
        const extendInstruction = web3_js_1.AddressLookupTableProgram.extendLookupTable({
            lookupTable: lookupTableAddress,
            authority: relayer.publicKey,
            payer: relayer.publicKey,
            addresses: lookupAddresses,
        });
        await anchor_1.web3.sendAndConfirmTransaction(connection, new anchor_1.web3.Transaction().add(extendInstruction), [relayer], {
            skipPreflight: true, // Avoids recent slot mismatch in simulation.
        });
        // Avoids invalid ALT index as ALT might not be active yet on the following tx.
        await new Promise((resolve) => setTimeout(resolve, 1000));
        // Fetch the AddressLookupTableAccount
        const lookupTableAccount = (await connection.getAddressLookupTable(lookupTableAddress)).value;
        if (lookupTableAccount === null)
            throw new Error("AddressLookupTableAccount not fetched");
        // Create the versioned transaction
        const versionedTx = new web3_js_1.VersionedTransaction(new web3_js_1.TransactionMessage({
            payerKey: relayer.publicKey,
            recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
            instructions: [createTokenAccountsInstruction, ...fillInstructions],
        }).compileToV0Message([lookupTableAccount]));
        // Sign and submit the versioned transaction.
        versionedTx.sign([relayer]);
        await connection.sendTransaction(versionedTx);
        // Verify balances after the fill
        await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for tx processing
        const fRelayerBal = (await (0, spl_token_1.getAccount)(connection, relayerTA)).amount;
        assertSE(fRelayerBal, iRelayerBal - BigInt(relayAmount * numberOfFills), "Relayer's balance should be reduced by total relay amount");
        recipientAssociatedTokens.forEach(async (recipientAssociatedToken) => {
            const recipientBal = (await (0, spl_token_1.getAccount)(connection, recipientAssociatedToken)).amount;
            assertSE(recipientBal, BigInt(relayAmount), "Recipient's balance should be increased by the relay amount");
        });
    });
});
