"use strict";
// This script bridges remote call to pause deposits on Solana Spoke Pool. Required environment:
// - ETHERS_PROVIDER_URL: Ethereum RPC provider URL.
// - ETHERS_MNEMONIC: Mnemonic of the wallet that will sign the sending transaction on Ethereum
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 });
require("dotenv/config");
const anchor = __importStar(require("@coral-xyz/anchor"));
const anchor_1 = require("@coral-xyz/anchor");
const web3_js_1 = require("@solana/web3.js");
const yargs_1 = __importDefault(require("yargs"));
const helpers_1 = require("yargs/helpers");
const ethers_1 = require("ethers");
const cctpHelpers_1 = require("../../test/svm/cctpHelpers");
// Set up Solana provider.
const provider = anchor_1.AnchorProvider.env();
anchor.setProvider(provider);
// Parse arguments
const argv = (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
    .option("seed", { type: "string", demandOption: true, describe: "Seed for the state account PDA" })
    .option("pause", { type: "boolean", demandOption: false, describe: "Enable or disable deposits" })
    .option("resumeRemoteTx", { type: "string", demandOption: false, describe: "Resume receiving remote tx" })
    .check((argv) => {
    if (argv.pause !== undefined && argv.resumeRemoteTx !== undefined) {
        throw new Error("Options --pause and --resumeRemoteTx are mutually exclusive");
    }
    if (argv.pause === undefined && argv.resumeRemoteTx === undefined) {
        throw new Error("One of the options --pause or --resumeRemoteTx is required");
    }
    return true;
}).argv;
async function remotePauseDeposits() {
    const resolvedArgv = await argv;
    const seed = new anchor_1.BN(resolvedArgv.seed);
    const pause = resolvedArgv.pause;
    const resumeRemoteTx = resolvedArgv.resumeRemoteTx;
    // Set up Ethereum provider.
    if (!process.env.ETHERS_PROVIDER_URL) {
        throw new Error("Environment variable ETHERS_PROVIDER_URL is not set");
    }
    const ethersProvider = new ethers_1.ethers.providers.JsonRpcProvider(process.env.ETHERS_PROVIDER_URL);
    if (!process.env.ETHERS_MNEMONIC) {
        throw new Error("Environment variable ETHERS_MNEMONIC is not set");
    }
    const ethersSigner = ethers_1.ethers.Wallet.fromMnemonic(process.env.ETHERS_MNEMONIC).connect(ethersProvider);
    // CCTP domains.
    const remoteDomain = 0; // Ethereum
    const localDomain = 5; // Solana
    // Get Solana programs and accounts.
    const svmSpokeIdl = require("../../target/idl/svm_spoke.json");
    const svmSpokeProgram = new anchor_1.Program(svmSpokeIdl, provider);
    const [statePda, _] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("state"), seed.toArrayLike(Buffer, "le", 8)], svmSpokeProgram.programId);
    const messageTransmitterIdl = require("../../target/idl/message_transmitter.json");
    const messageTransmitterProgram = new anchor_1.Program(messageTransmitterIdl, provider);
    const [messageTransmitterState] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("message_transmitter")], messageTransmitterProgram.programId);
    const [authorityPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("message_transmitter_authority"), svmSpokeProgram.programId.toBuffer()], messageTransmitterProgram.programId);
    const [selfAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("self_authority")], svmSpokeProgram.programId);
    const [eventAuthority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], svmSpokeProgram.programId);
    let cluster;
    const rpcEndpoint = provider.connection.rpcEndpoint;
    if (rpcEndpoint.includes("devnet"))
        cluster = "devnet";
    else if (rpcEndpoint.includes("mainnet"))
        cluster = "mainnet";
    else
        throw new Error(`Unsupported cluster endpoint: ${rpcEndpoint}`);
    const irisApiUrl = cluster == "devnet" ? "https://iris-api-sandbox.circle.com" : "https://iris-api.circle.com";
    const supportedChainId = cluster == "devnet" ? 11155111 : 1; // Sepolia is bridged to devnet, Ethereum to mainnet in CCTP.
    const chainId = (await ethersProvider.getNetwork()).chainId;
    // TODO: improve type casting.
    if (chainId !== BigInt(supportedChainId)) {
        throw new Error(`Chain ID ${chainId} does not match expected Solana cluster ${cluster}`);
    }
    const messageTransmitterRemoteIface = new ethers_1.ethers.utils.Interface([
        "function sendMessage(uint32 destinationDomain, bytes32 recipient, bytes messageBody)",
        "event MessageSent(bytes message)",
    ]);
    // CCTP MessageTransmitter from https://developers.circle.com/stablecoins/docs/evm-smart-contracts
    const messageTransmitterRemoteAddress = cluster == "devnet" ? "0x7865fAfC2db2093669d92c0F33AeEF291086BEFD" : "0x0a992d191deec32afe36203ad87d7d289a738f81";
    const messageTransmitterRemote = new ethers_1.ethers.Contract(messageTransmitterRemoteAddress, messageTransmitterRemoteIface, ethersSigner);
    const spokePoolIface = new ethers_1.ethers.utils.Interface(["function pauseDeposits(bool pause)"]);
    console.log("Remotely controlling pausedDeposits...");
    console.table([
        { Property: "seed", Value: seed.toString() },
        { Property: "chainId", Value: chainId.toString() },
        { Property: "pause", Value: pause },
        { Property: "svmSpokeProgramProgramId", Value: svmSpokeProgram.programId.toString() },
        { Property: "providerPublicKey", Value: provider.wallet.publicKey.toString() },
        { Property: "statePda", Value: statePda.toString() },
        { Property: "messageTransmitterProgramId", Value: messageTransmitterProgram.programId.toString() },
        { Property: "messageTransmitterState", Value: messageTransmitterState.toString() },
        { Property: "authorityPda", Value: authorityPda.toString() },
        { Property: "selfAuthority", Value: selfAuthority.toString() },
        { Property: "eventAuthority", Value: eventAuthority.toString() },
        { Property: "messageTransmitterRemoteAddress", Value: messageTransmitterRemoteAddress },
        { Property: "remoteSender", Value: ethersSigner.address },
    ]);
    // Send pauseDeposits call from Ethereum, unless resuming a remote transaction.
    let remoteTxHash;
    if (!resumeRemoteTx) {
        console.log("Sending pauseDeposits message from remote domain...");
        const calldata = spokePoolIface.encodeFunctionData("pauseDeposits", [pause]);
        const sendTx = await messageTransmitterRemote.sendMessage.send(localDomain, svmSpokeProgram.programId.toBytes(), calldata);
        await sendTx.wait();
        remoteTxHash = sendTx.hash;
        console.log("Message sent on remote chain, tx", remoteTxHash);
    }
    else
        remoteTxHash = resumeRemoteTx;
    // Fetch attestation from CCTP attestation service.
    const attestationResponse = await (0, cctpHelpers_1.getMessages)(remoteTxHash, remoteDomain, irisApiUrl);
    const { attestation, message } = attestationResponse.messages[0];
    console.log("CCTP attestation response:", attestationResponse.messages[0]);
    // Accounts in CCTP message_transmitter receive_message instruction.
    const nonce = (0, cctpHelpers_1.decodeMessageHeader)(Buffer.from(message.replace("0x", ""), "hex")).nonce;
    const usedNonces = (await messageTransmitterProgram.methods
        .getNoncePda({
        nonce: new anchor_1.BN(nonce.toString()),
        sourceDomain: remoteDomain,
    })
        .accounts({
        messageTransmitter: messageTransmitterState,
    })
        .view());
    const receiveMessageAccounts = {
        payer: provider.wallet.publicKey,
        caller: provider.wallet.publicKey,
        authorityPda,
        messageTransmitter: messageTransmitterState,
        usedNonces,
        receiver: svmSpokeProgram.programId,
        systemProgram: anchor_1.web3.SystemProgram.programId,
    };
    // accountMetas list to pass to remaining accounts when receiving message via CCTP.
    const remainingAccounts = [];
    // state in HandleReceiveMessage accounts (used for remote domain and sender authentication).
    remainingAccounts.push({
        isSigner: false,
        isWritable: false,
        pubkey: statePda,
    });
    // self_authority in HandleReceiveMessage accounts, also signer in self-invoked CPIs.
    remainingAccounts.push({
        isSigner: false,
        isWritable: false,
        pubkey: selfAuthority,
    });
    // program in HandleReceiveMessage accounts.
    remainingAccounts.push({
        isSigner: false,
        isWritable: false,
        pubkey: svmSpokeProgram.programId,
    });
    // state in self-invoked CPIs (state can change as a result of remote call).
    remainingAccounts.push({
        isSigner: false,
        isWritable: true,
        pubkey: statePda,
    });
    // event_authority in self-invoked CPIs (appended by Anchor with event_cpi macro).
    remainingAccounts.push({
        isSigner: false,
        isWritable: false,
        pubkey: eventAuthority,
    });
    // program in self-invoked CPIs (appended by Anchor with event_cpi macro).
    remainingAccounts.push({
        isSigner: false,
        isWritable: false,
        pubkey: svmSpokeProgram.programId,
    });
    // Receive remote message on Solana.
    console.log("Receiving message on Solana...");
    const receiveMessageTx = await messageTransmitterProgram.methods
        .receiveMessage({
        message: Buffer.from(message.replace("0x", ""), "hex"),
        attestation: Buffer.from(attestation.replace("0x", ""), "hex"),
    })
        .accounts(receiveMessageAccounts)
        .remainingAccounts(remainingAccounts)
        .rpc();
    console.log("\nReceived remote message");
    console.log("Your transaction signature", receiveMessageTx);
    // Check updated state.
    const stateData = await svmSpokeProgram.account.state.fetch(statePda);
    console.log("Updated pausedDeposits state to:", stateData.pausedDeposits);
}
remotePauseDeposits()
    .then(() => {
    process.exit(0);
})
    .catch((err) => {
    console.error(err);
    process.exit(1);
});
