"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 web3_v1_1 = require("../../src/svm/web3-v1");
const SvmSpoke_common_1 = require("./SvmSpoke.common");
const utils_1 = require("./utils");
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();
    const { encodedMessage, fillRemainingAccounts } = (0, utils_1.testAcrossPlusMessage)();
    const tokenDecimals = 6;
    let state, mint, relayerTA, recipientTA, otherRelayerTA, tokenProgram;
    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, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId);
        const [fillStatusPDA] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fills"), relayHashUint8Array], program.programId);
        accounts = {
            state,
            signer: relayer.publicKey,
            instructionParams: program.programId,
            mint: mint,
            relayerTokenAccount: relayerTA,
            recipientTokenAccount: recipientTA,
            fillStatus: fillStatusPDA,
            tokenProgram: tokenProgram ?? spl_token_1.TOKEN_PROGRAM_ID,
            associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
            systemProgram: anchor.web3.SystemProgram.programId,
            program: program.programId,
        };
    }
    const approvedFillRelay = async (fillDataValues, calledFillAccounts = accounts, callingRelayer = relayer) => {
        // Delegate state PDA to pull relayer tokens.
        const approveIx = await (0, spl_token_1.createApproveCheckedInstruction)(calledFillAccounts.relayerTokenAccount, calledFillAccounts.mint, calledFillAccounts.state, calledFillAccounts.signer, BigInt(fillDataValues[1].outputAmount.toString()), tokenDecimals, undefined, tokenProgram);
        const fillIx = await program.methods
            .fillRelay(...fillDataValues)
            .accounts(calledFillAccounts)
            .remainingAccounts(fillRemainingAccounts)
            .instruction();
        const fillTx = new web3_js_1.Transaction().add(approveIx, fillIx);
        const tx = await (0, web3_js_1.sendAndConfirmTransaction)(connection, fillTx, [payer, callingRelayer]);
        return tx;
    };
    before("Funds relayer wallets", async () => {
        await connection.requestAirdrop(relayer.publicKey, 10_000_000_000); // 10 SOL
        await connection.requestAirdrop(otherRelayer.publicKey, 10_000_000_000); // 10 SOL
    });
    beforeEach(async () => {
        mint = await (0, spl_token_1.createMint)(connection, payer, owner, owner, tokenDecimals);
        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());
        tokenProgram = spl_token_1.TOKEN_PROGRAM_ID; // Some tests might override this.
        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: (0, web3_v1_1.intToU8Array32)(Math.floor(Math.random() * 1000000)), // force that we always have a new deposit id.
            fillDeadline: Math.floor(Date.now() / 1000) + 60, // 1 minute from now
            exclusivityDeadline: Math.floor(Date.now() / 1000) + 30, // 30 seconds from now
            message: encodedMessage,
        };
        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, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId));
        await approvedFillRelay([relayHash, relayData, new anchor_1.BN(1), relayer.publicKey]);
        // 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 FilledRelay event after filling a relay", async () => {
        const relayHash = Array.from((0, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId));
        const tx = await approvedFillRelay([relayHash, relayData, new anchor_1.BN(420), otherRelayer.publicKey]);
        // Fetch and verify the FilledRelay event
        const events = await (0, web3_v1_1.readEventsUntilFound)(connection, tx, [program]);
        const event = events.find((event) => event.name === "filledRelay")?.data;
        assert.isNotNull(event, "FilledRelay event should be emitted");
        // Verify that the event data matches the relay data.
        Object.entries(relayData).forEach(([key, value]) => {
            if (key === "message") {
                assertSE(event.messageHash, (0, web3_v1_1.hashNonEmptyMessage)(value), `MessageHash should match`);
            }
            else
                assertSE(event[key], value, `${key.charAt(0).toUpperCase() + key.slice(1)} should match`);
        });
        // RelayExecutionInfo should match.
        assertSE(event.relayExecutionInfo.updatedRecipient, relayData.recipient, "UpdatedRecipient should match");
        assertSE(event.relayExecutionInfo.updatedMessageHash, (0, web3_v1_1.hashNonEmptyMessage)(relayData.message), "UpdatedMessageHash should match");
        assertSE(event.relayExecutionInfo.updatedOutputAmount, relayData.outputAmount, "UpdatedOutputAmount should match");
        assert.equal(JSON.stringify(event.relayExecutionInfo.fillType), `{"fastFill":{}}`, "FillType should be FastFill");
        // 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: Math.floor(Date.now() / 1000) - 69 }); // 69 seconds ago
        const relayHash = Array.from((0, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId));
        try {
            await approvedFillRelay([relayHash, relayData, new anchor_1.BN(1), relayer.publicKey]);
            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, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId));
        try {
            await approvedFillRelay([relayHash, relayData, new anchor_1.BN(1), relayer.publicKey], undefined, otherRelayer);
            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: 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, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId));
        await approvedFillRelay([relayHash, relayData, new anchor_1.BN(1), relayer.publicKey], undefined, otherRelayer);
        // 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, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId));
        // First fill attempt
        await approvedFillRelay([relayHash, relayData, new anchor_1.BN(1), relayer.publicKey]);
        // Second fill attempt with the same data
        try {
            await approvedFillRelay([relayHash, relayData, new anchor_1.BN(1), relayer.publicKey]);
            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, web3_v1_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 approvedFillRelay([relayHash, relayData, new anchor_1.BN(1), relayer.publicKey]);
        // 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().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, new anchor_1.BN(relayData.fillDeadline + 1));
        // Close the fill PDA
        await program.methods.closeFillPda().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, web3_v1_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 approvedFillRelay([Array.from(relayHash), relayData, new anchor_1.BN(1), relayer.publicKey]);
        // 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, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId));
        try {
            await approvedFillRelay([relayHash, relayData, new anchor_1.BN(1), relayer.publicKey]);
            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, web3_v1_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 approvedFillRelay([Array.from(relayHash), relayData, new anchor_1.BN(1), relayer.publicKey], {
                ...accounts,
                recipientTokenAccount: wrongRecipientTA,
                fillStatus: wrongFillStatus,
            });
            assert.fail("Should not be able to fill relay to wrong recipient token account");
        }
        catch (err) {
            assert.include(err.toString(), "ConstraintTokenOwner", "Expected ConstraintTokenOwner error");
        }
    });
    it("Fails to fill a relay for mint inconsistent output_token", async () => {
        const relayHash = (0, web3_v1_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, tokenDecimals);
        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 approvedFillRelay([Array.from(relayHash), relayData, new anchor_1.BN(1), relayer.publicKey], {
                ...accounts,
                mint: wrongMint,
                relayerTokenAccount: wrongRelayerTA,
                recipientTokenAccount: wrongRecipientTA,
            });
            assert.fail("Should not be able to process fill for inconsistent mint");
        }
        catch (err) {
            assert.include(err.toString(), "InvalidMint", "Expected InvalidMint error");
        }
    });
    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, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId));
        await approvedFillRelay([relayHash, relayData, new anchor_1.BN(1), relayer.publicKey]);
        // 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: (0, web3_v1_1.intToU8Array32)(Math.floor(Math.random() * 1000000)),
        };
        updateRelayData(newRelayData);
        accounts.recipientTokenAccount = newRecipientATA;
        const relayHash = Array.from((0, web3_v1_1.calculateRelayHashUint8Array)(newRelayData, chainId));
        try {
            await approvedFillRelay([relayHash, newRelayData, new anchor_1.BN(1), relayer.publicKey]);
            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 approveInstruction = await (0, spl_token_1.createApproveCheckedInstruction)(accounts.relayerTokenAccount, accounts.mint, accounts.state, accounts.signer, BigInt(newRelayData.outputAmount.toString()), tokenDecimals, undefined, tokenProgram);
        const fillInstruction = await program.methods
            .fillRelay(relayHash, newRelayData, new anchor_1.BN(1), relayer.publicKey)
            .accounts(accounts)
            .remainingAccounts(fillRemainingAccounts)
            .instruction();
        // Create and send the transaction
        const transaction = new anchor_1.web3.Transaction().add(createTokenAccountsInstruction, approveInstruction, 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 createTAremainingAccounts = 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(createTAremainingAccounts)
            .instruction();
        // Build instructions for all fills
        let totalFillAmount = new anchor_1.BN(0);
        const fillInstructions = [];
        for (let i = 0; i < numberOfFills; i++) {
            const newRelayData = {
                ...relayData,
                recipient: recipientAuthorities[i],
                depositId: (0, web3_v1_1.intToU8Array32)(Math.floor(Math.random() * 1000000)),
            };
            totalFillAmount = totalFillAmount.add(newRelayData.outputAmount);
            updateRelayData(newRelayData);
            accounts.recipientTokenAccount = recipientAssociatedTokens[i];
            const relayHash = Array.from((0, web3_v1_1.calculateRelayHashUint8Array)(newRelayData, chainId));
            const fillInstruction = await program.methods
                .fillRelay(relayHash, newRelayData, new anchor_1.BN(1), relayer.publicKey)
                .accounts(accounts)
                .remainingAccounts(fillRemainingAccounts)
                .instruction();
            fillInstructions.push(fillInstruction);
        }
        const approveInstruction = await (0, spl_token_1.createApproveCheckedInstruction)(accounts.relayerTokenAccount, accounts.mint, accounts.state, accounts.signer, BigInt(totalFillAmount.toString()), tokenDecimals, undefined, tokenProgram);
        // Fill using the ALT.
        await (0, web3_v1_1.sendTransactionWithLookupTable)(connection, [createTokenAccountsInstruction, approveInstruction, ...fillInstructions], relayer);
        // Verify balances after the fill
        await new Promise((resolve) => setTimeout(resolve, 500)); // 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");
        });
    });
    it("Fills a relay with enabled CPI-guard", async () => {
        // CPI-guard is available only for the 2022 token program.
        tokenProgram = spl_token_1.TOKEN_2022_PROGRAM_ID;
        // Remint the tokens on the token 2022 program.
        mint = await (0, spl_token_1.createMint)(connection, payer, owner, owner, tokenDecimals, undefined, undefined, tokenProgram);
        recipientTA = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(connection, payer, mint, recipient, undefined, undefined, undefined, tokenProgram)).address;
        relayerTA = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(connection, payer, mint, relayer.publicKey, undefined, undefined, undefined, tokenProgram)).address;
        await (0, spl_token_1.mintTo)(connection, payer, mint, relayerTA, owner, seedBalance, undefined, undefined, tokenProgram);
        // Update relay data with new mint.
        relayData.outputToken = mint;
        updateRelayData(relayData);
        // Enable CPI-guard for the relayer (requires TA reallocation).
        const enableCpiGuardTx = new web3_js_1.Transaction().add((0, spl_token_1.createReallocateInstruction)(relayerTA, relayer.publicKey, [spl_token_1.ExtensionType.CpiGuard], relayer.publicKey), (0, spl_token_1.createEnableCpiGuardInstruction)(relayerTA, relayer.publicKey));
        await (0, web3_js_1.sendAndConfirmTransaction)(connection, enableCpiGuardTx, [relayer]);
        // Verify recipient's balance before the fill
        let recipientAccount = await (0, spl_token_1.getAccount)(connection, recipientTA, undefined, tokenProgram);
        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, undefined, tokenProgram);
        assertSE(relayerAccount.amount, seedBalance, "Relayer's balance should be equal to seed balance before the fill");
        const relayHash = Array.from((0, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId));
        await approvedFillRelay([relayHash, relayData, new anchor_1.BN(1), relayer.publicKey]);
        // Verify relayer's balance after the fill
        relayerAccount = await (0, spl_token_1.getAccount)(connection, relayerTA, undefined, tokenProgram);
        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, undefined, tokenProgram);
        assertSE(recipientAccount.amount, relayAmount, "Recipient's balance should be increased by the relay amount");
    });
    it("Emits zeroed hash for empty message", async () => {
        updateRelayData({ ...relayData, message: Buffer.alloc(0) });
        const relayHash = Array.from((0, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId));
        const tx = await approvedFillRelay([relayHash, relayData, new anchor_1.BN(420), otherRelayer.publicKey]);
        // Fetch and verify the FilledRelay event
        const events = await (0, web3_v1_1.readEventsUntilFound)(connection, tx, [program]);
        const event = events.find((event) => event.name === "filledRelay")?.data;
        assert.isNotNull(event, "FilledRelay event should be emitted");
        // Verify that the event data has zeroed message hash.
        assertSE(event.messageHash, new Uint8Array(32), `MessageHash should be zeroed`);
        assertSE(event.relayExecutionInfo.updatedMessageHash, new Uint8Array(32), `UpdatedMessageHash should be zeroed`);
    });
});
