"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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
// This script implements a fill where relayed tokens are distributed to random recipients via the message handler.
// Note that this should be run only on devnet as this is fake fill and all filled tokens are sent to random recipients.
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 yargs_1 = __importDefault(require("yargs"));
const helpers_1 = require("yargs/helpers");
const web3_v1_1 = require("../../src/svm/web3-v1");
// Set up the provider and signer.
const provider = anchor_1.AnchorProvider.env();
anchor.setProvider(provider);
const signer = anchor.AnchorProvider.env().wallet.payer;
const program = (0, web3_v1_1.getSpokePoolProgram)(provider);
const programId = program.programId;
// Parse arguments
const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
    .option("depositor", { type: "string", demandOption: true, describe: "Depositor public key" })
    .option("handler", { type: "string", demandOption: true, describe: "Handler program ID" })
    .option("exclusiveRelayer", { type: "string", demandOption: false, describe: "Exclusive relayer public key" })
    .option("inputToken", { type: "string", demandOption: true, describe: "Input token public key" })
    .option("outputToken", { type: "string", demandOption: true, describe: "Output token public key" })
    .option("inputAmount", { type: "number", demandOption: true, describe: "Input amount" })
    .option("outputAmount", { type: "number", demandOption: true, describe: "Output amount" })
    .option("originChainId", { type: "string", demandOption: true, describe: "Origin chain ID" })
    .option("depositId", { type: "array", demandOption: true, describe: "Deposit ID" })
    .option("fillDeadline", { type: "number", demandOption: false, describe: "Fill deadline" })
    .option("exclusivityDeadline", { type: "number", demandOption: false, describe: "Exclusivity deadline" })
    .option("repaymentChain", { type: "number", demandOption: false, description: "Repayment chain ID" })
    .option("repaymentAddress", { type: "string", demandOption: false, description: "Repayment address" })
    .option("distributionCount", { type: "number", demandOption: false, describe: "Distribution count" })
    .option("bufferParams", { type: "boolean", demandOption: false, describe: "Use buffer account for params" }).argv;
async function fillV3RelayToRandom() {
    const resolvedArgv = await argv;
    const depositor = new web3_js_1.PublicKey(resolvedArgv.depositor);
    const handler = new web3_js_1.PublicKey(resolvedArgv.handler);
    const exclusiveRelayer = new web3_js_1.PublicKey(resolvedArgv.exclusiveRelayer || web3_js_1.PublicKey.default.toString());
    const inputToken = new web3_js_1.PublicKey(resolvedArgv.inputToken);
    const outputToken = new web3_js_1.PublicKey(resolvedArgv.outputToken);
    const inputAmount = new anchor_1.BN(resolvedArgv.inputAmount);
    const outputAmount = new anchor_1.BN(resolvedArgv.outputAmount);
    const originChainId = new anchor_1.BN(resolvedArgv.originChainId);
    const depositId = resolvedArgv.depositId.map((id) => id); // Ensure depositId is an array of BN
    const fillDeadline = resolvedArgv.fillDeadline || Math.floor(Date.now() / 1000) + 60; // Current time + 1 minute
    const exclusivityDeadline = resolvedArgv.exclusivityDeadline || Math.floor(Date.now() / 1000) + 30; // Current time + 30 seconds
    const repaymentChain = new anchor_1.BN(resolvedArgv.repaymentChain || 1);
    const repaymentAddress = new web3_js_1.PublicKey(resolvedArgv.repaymentAddress || signer.publicKey.toString());
    const seed = new anchor_1.BN(0);
    const distributionCount = resolvedArgv.distributionCount || 1;
    const bufferParams = resolvedArgv.bufferParams || false;
    const tokenDecimals = (await (0, spl_token_1.getMint)(provider.connection, outputToken)).decimals;
    // Filled relay will first send tokens to the handler ATA.
    const [handlerSigner] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("handler_signer")], handler);
    const handlerATA = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(provider.connection, signer, outputToken, handlerSigner, true)).address;
    // Populate random final recipients and transfer instructions.
    const recipientAccounts = [];
    const transferInstructions = [];
    for (let i = 0; i < distributionCount; i++) {
        const recipient = web3_js_1.Keypair.generate().publicKey;
        const recipientATA = (await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(provider.connection, signer, outputToken, recipient))
            .address;
        recipientAccounts.push(recipientATA);
        // Construct ix to transfer tokens from handler to the recipient in equal distribution (except the last one getting any rounding remainder).
        const distributionAmount = i !== distributionCount - 1
            ? outputAmount.div(new anchor_1.BN(distributionCount))
            : outputAmount.sub(outputAmount.div(new anchor_1.BN(distributionCount)).mul(new anchor_1.BN(distributionCount - 1)));
        const transferInstruction = (0, spl_token_1.createTransferCheckedInstruction)(handlerATA, outputToken, recipientATA, handlerSigner, BigInt(distributionAmount.toString()), tokenDecimals);
        transferInstructions.push(transferInstruction);
    }
    // Encode handler message for the token distribution.
    const multicallHandlerCoder = new web3_v1_1.MulticallHandlerCoder(transferInstructions);
    const handlerMessage = multicallHandlerCoder.encode();
    const message = new web3_v1_1.AcrossPlusMessageCoder({
        handler,
        readOnlyLen: multicallHandlerCoder.readOnlyLen,
        valueAmount: new anchor_1.BN(0),
        accounts: multicallHandlerCoder.compiledMessage.accountKeys,
        handlerMessage,
    });
    const encodedMessage = message.encode();
    const relayData = {
        depositor,
        recipient: handlerSigner,
        exclusiveRelayer,
        inputToken,
        outputToken,
        inputAmount,
        outputAmount,
        originChainId,
        depositId,
        fillDeadline,
        exclusivityDeadline,
        message: encodedMessage,
    };
    console.log("Filling V3 Relay with handler...");
    // Define the state account PDA
    const [statePda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("state"), seed.toArrayLike(Buffer, "le", 8)], programId);
    // Fetch the state from the on-chain program to get chainId
    const state = await program.account.state.fetch(statePda);
    const chainId = new anchor_1.BN(state.chainId);
    const relayHashUint8Array = (0, web3_v1_1.calculateRelayHashUint8Array)(relayData, chainId);
    const relayHash = Array.from(relayHashUint8Array);
    // Define the fill status account PDA
    const [fillStatusPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("fills"), relayHashUint8Array], programId);
    // Get ATA for the relayer token account
    const relayerTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(outputToken, signer.publicKey);
    console.table([
        { property: "relayHash", value: Buffer.from(relayHashUint8Array).toString("hex") },
        { property: "chainId", value: chainId.toString() },
        { property: "programId", value: programId.toString() },
        { property: "providerPublicKey", value: provider.wallet.publicKey.toString() },
        { property: "statePda", value: statePda.toString() },
        { property: "fillStatusPda", value: fillStatusPda.toString() },
        { property: "relayerTokenAccount", value: relayerTokenAccount.toString() },
        { property: "recipientTokenAccount", value: handlerATA.toString() },
        { property: "seed", value: seed.toString() },
    ]);
    console.log("Relay Data:");
    console.table(Object.entries(relayData).map(([key, value]) => ({
        key,
        value: key === "message" ? value.toString("hex") : value.toString(),
    })));
    // Delegate state PDA to pull relayer tokens.
    const approveInstruction = await (0, spl_token_1.createApproveCheckedInstruction)(relayerTokenAccount, outputToken, statePda, signer.publicKey, BigInt(relayData.outputAmount.toString()), tokenDecimals, undefined, spl_token_1.TOKEN_PROGRAM_ID);
    // Prepare fill instruction as we will need to use Address Lookup Table (ALT).
    const fillV3RelayValues = [relayHash, relayData, repaymentChain, repaymentAddress];
    if (bufferParams) {
        await (0, web3_v1_1.loadFillRelayParams)(program, signer, fillV3RelayValues[1], fillV3RelayValues[2], fillV3RelayValues[3]);
    }
    const fillV3RelayParams = bufferParams ? [fillV3RelayValues[0], null, null, null] : fillV3RelayValues;
    const [instructionParams] = bufferParams
        ? web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("instruction_params"), signer.publicKey.toBuffer()], program.programId)
        : [program.programId];
    const fillAccounts = {
        state: statePda,
        signer: signer.publicKey,
        instructionParams,
        mint: outputToken,
        relayerTokenAccount,
        recipientTokenAccount: handlerATA,
        fillStatus: fillStatusPda,
        tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
        associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
        systemProgram: web3_js_1.SystemProgram.programId,
        program: programId,
    };
    const remainingAccounts = [
        { pubkey: handler, isSigner: false, isWritable: false },
        ...multicallHandlerCoder.compiledKeyMetas,
    ];
    const fillInstruction = await program.methods
        .fillRelay(...fillV3RelayParams)
        .accounts(fillAccounts)
        .remainingAccounts(remainingAccounts)
        .instruction();
    // Fill using the ALT.
    const { txSignature } = await (0, web3_v1_1.sendTransactionWithLookupTable)(provider.connection, [approveInstruction, fillInstruction], signer);
    console.log("Transaction signature:", txSignature);
}
// Run the fillV3RelayToRandom function
fillV3RelayToRandom();
