"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 });
exports.AttestationResponse = exports.AttestationMessage = exports.DecodedMessage = exports.DecodedMessageBody = exports.decodeTokenMessengerV2MessageBody = exports.decodeMessageHeaderV2 = exports.decodeMessageSentDataV2 = exports.EVENT_ACCOUNT_WINDOW_SECONDS = void 0;
exports.getV2BurnAttestation = getV2BurnAttestation;
const anchor = __importStar(require("@coral-xyz/anchor"));
const superstruct_1 = require("superstruct");
const ethers_1 = require("ethers");
const superstruct_2 = require("superstruct");
const relayHashUtils_1 = require("./relayHashUtils");
const conversionUtils_1 = require("./conversionUtils");
// Index positions to decode Message Header from
// https://developers.circle.com/cctp/technical-guide#message-header
const HEADER_VERSION_INDEX = 0;
const SOURCE_DOMAIN_INDEX = 4;
const DESTINATION_DOMAIN_INDEX = 8;
const NONCE_INDEX = 12;
const HEADER_SENDER_INDEX = 44;
const HEADER_RECIPIENT_INDEX = 76;
const DESTINATION_CALLER_INDEX = 108;
const MIN_FINALITY_THRESHOLD_INDEX = 140;
const FINALITY_THRESHOLD_EXECUTED_INDEX = 144;
const MESSAGE_BODY_INDEX = 148;
// Index positions to decode Message Body for TokenMessengerV2 from
// https://developers.circle.com/cctp/technical-guide#message-body
const BODY_VERSION_INDEX = 0;
const BURN_TOKEN_INDEX = 4;
const MINT_RECIPIENT_INDEX = 36;
const AMOUNT_INDEX = 68;
const MESSAGE_SENDER_INDEX = 100;
const MAX_FEE_INDEX = 132;
const FEE_EXECUTED_INDEX = 164;
const EXPIRATION_BLOCK = 196;
const HOOK_DATA_INDEX = 228;
exports.EVENT_ACCOUNT_WINDOW_SECONDS = 60 * 60 * 24 * 5; // 60 secs * 60 mins * 24 hours * 5 days = 5 days in seconds
/**
 * Decodes a CCTPv2 message into a MessageHeaderV2 and TokenMessengerV2MessageBody.
 */
const decodeMessageSentDataV2 = (message) => {
    const messageHeader = (0, exports.decodeMessageHeaderV2)(message);
    const messageBodyData = message.slice(MESSAGE_BODY_INDEX);
    const messageBody = (0, exports.decodeTokenMessengerV2MessageBody)(messageBodyData);
    return { ...messageHeader, messageBody };
};
exports.decodeMessageSentDataV2 = decodeMessageSentDataV2;
/**
 * Decodes a CCTPv2 message header.
 */
const decodeMessageHeaderV2 = (data) => {
    const version = data.readUInt32BE(HEADER_VERSION_INDEX);
    const sourceDomain = data.readUInt32BE(SOURCE_DOMAIN_INDEX);
    const destinationDomain = data.readUInt32BE(DESTINATION_DOMAIN_INDEX);
    const nonce = (0, relayHashUtils_1.readUInt256BE)(data.slice(NONCE_INDEX, NONCE_INDEX + 32));
    const sender = new anchor.web3.PublicKey(data.slice(HEADER_SENDER_INDEX, HEADER_SENDER_INDEX + 32));
    const recipient = new anchor.web3.PublicKey(data.slice(HEADER_RECIPIENT_INDEX, HEADER_RECIPIENT_INDEX + 32));
    const destinationCaller = new anchor.web3.PublicKey(data.slice(DESTINATION_CALLER_INDEX, DESTINATION_CALLER_INDEX + 32));
    const minFinalityThreshold = data.readUInt32BE(MIN_FINALITY_THRESHOLD_INDEX);
    const finalityThresholdExecuted = data.readUInt32BE(FINALITY_THRESHOLD_EXECUTED_INDEX);
    const messageBody = data.slice(MESSAGE_BODY_INDEX);
    return {
        version,
        sourceDomain,
        destinationDomain,
        nonce,
        sender,
        recipient,
        destinationCaller,
        minFinalityThreshold,
        finalityThresholdExecuted,
        messageBody,
    };
};
exports.decodeMessageHeaderV2 = decodeMessageHeaderV2;
/**
 * Decodes a TokenMessenger message body.
 */
const decodeTokenMessengerV2MessageBody = (data) => {
    const version = data.readUInt32BE(BODY_VERSION_INDEX);
    const burnToken = new anchor.web3.PublicKey(data.slice(BURN_TOKEN_INDEX, BURN_TOKEN_INDEX + 32));
    const mintRecipient = new anchor.web3.PublicKey(data.slice(MINT_RECIPIENT_INDEX, MINT_RECIPIENT_INDEX + 32));
    const amount = (0, relayHashUtils_1.readUInt256BE)(data.slice(AMOUNT_INDEX, AMOUNT_INDEX + 32));
    const messageSender = new anchor.web3.PublicKey(data.slice(MESSAGE_SENDER_INDEX, MESSAGE_SENDER_INDEX + 32));
    const maxFee = (0, relayHashUtils_1.readUInt256BE)(data.slice(MAX_FEE_INDEX, MAX_FEE_INDEX + 32));
    const feeExecuted = (0, relayHashUtils_1.readUInt256BE)(data.slice(FEE_EXECUTED_INDEX, FEE_EXECUTED_INDEX + 32));
    const expirationBlock = (0, relayHashUtils_1.readUInt256BE)(data.slice(EXPIRATION_BLOCK, EXPIRATION_BLOCK + 32));
    const hookData = data.slice(HOOK_DATA_INDEX);
    return { version, burnToken, mintRecipient, amount, messageSender, maxFee, feeExecuted, expirationBlock, hookData };
};
exports.decodeTokenMessengerV2MessageBody = decodeTokenMessengerV2MessageBody;
// Below structs defines the types for CCTP attestation API as documented in
// https://developers.circle.com/api-reference/cctp/all/get-messages-v-2
// DecodedMessage.decodedMessageBody (V1/V2; some fields V2-only)
exports.DecodedMessageBody = (0, superstruct_1.object)({
    burnToken: (0, superstruct_1.string)(),
    mintRecipient: (0, superstruct_1.string)(),
    amount: (0, superstruct_1.string)(),
    messageSender: (0, superstruct_1.string)(),
    // V2-only
    maxFee: (0, superstruct_1.optional)((0, superstruct_1.string)()),
    feeExecuted: (0, superstruct_1.optional)((0, superstruct_1.string)()),
    expirationBlock: (0, superstruct_1.optional)((0, superstruct_1.string)()),
    hookData: (0, superstruct_1.optional)((0, superstruct_1.string)()),
});
// DecodedMessage (nullable/empty if decoding fails)
// minFinalityThreshold & finalityThresholdExecuted are V2-only
exports.DecodedMessage = (0, superstruct_1.object)({
    sourceDomain: (0, superstruct_1.string)(),
    destinationDomain: (0, superstruct_1.string)(),
    nonce: (0, superstruct_1.string)(),
    sender: (0, superstruct_1.string)(),
    recipient: (0, superstruct_1.string)(),
    destinationCaller: (0, superstruct_1.string)(),
    minFinalityThreshold: (0, superstruct_1.optional)((0, superstruct_1.enums)(["1000", "2000"])),
    finalityThresholdExecuted: (0, superstruct_1.optional)((0, superstruct_1.enums)(["1000", "2000"])),
    messageBody: (0, superstruct_1.string)(),
    decodedMessageBody: (0, superstruct_1.optional)((0, superstruct_1.coerce)((0, superstruct_1.nullable)(exports.DecodedMessageBody), (0, superstruct_1.union)([(0, superstruct_1.nullable)(exports.DecodedMessageBody), (0, superstruct_1.object)({})]), (v) => isEmptyObject(v) ? null : v)),
});
// Each message item
exports.AttestationMessage = (0, superstruct_1.object)({
    message: (0, superstruct_1.string)(), // "0x" when not available
    eventNonce: (0, superstruct_1.string)(),
    attestation: (0, superstruct_1.string)(), // "PENDING" when not available
    decodedMessage: (0, superstruct_1.optional)((0, superstruct_1.coerce)((0, superstruct_1.nullable)(exports.DecodedMessage), (0, superstruct_1.union)([(0, superstruct_1.nullable)(exports.DecodedMessage), (0, superstruct_1.object)({})]), (v) => isEmptyObject(v) ? null : v)),
    cctpVersion: (0, superstruct_1.enums)([1, 2]),
    status: (0, superstruct_1.enums)(["complete", "pending_confirmations"]),
    // Only present in some delayed cases
    delayReason: (0, superstruct_1.optional)((0, superstruct_1.nullable)((0, superstruct_1.enums)(["insufficient_fee", "amount_above_max", "insufficient_allowance_available"]))),
});
// Top-level 200 response
exports.AttestationResponse = (0, superstruct_1.object)({
    messages: (0, superstruct_1.array)(exports.AttestationMessage),
});
const isEmptyObject = (v) => v != null && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0;
/**
 * Fetches attestation from attestation service given the txHash and source message for CCTP V2 token burn.
 */
async function getV2BurnAttestation(txSignature, sourceMessageData, irisApiUrl) {
    const sourceMessage = (0, exports.decodeMessageSentDataV2)(sourceMessageData);
    const attestationResponse = await (await fetch(`${irisApiUrl}/v2/messages/${sourceMessage.sourceDomain}/?transactionHash=${txSignature}`)).json();
    if (attestationResponse.error)
        return null;
    (0, superstruct_2.assert)(attestationResponse, exports.AttestationResponse);
    // Return the first attested message that matches the source message.
    for (const message of attestationResponse.messages) {
        if (message.message !== "0x" &&
            message.attestation !== "PENDING" &&
            !!message.decodedMessage &&
            isMatchingV2BurnMessage(sourceMessage, message.decodedMessage)) {
            return {
                destinationMessage: Buffer.from(ethers_1.ethers.utils.arrayify(message.message)),
                attestation: Buffer.from(ethers_1.ethers.utils.arrayify(message.attestation)),
            };
        }
    }
    return null;
}
function isMatchingV2BurnMessage(sourceMessage, destinationMessage) {
    if (!destinationMessage.decodedMessageBody)
        return false;
    return (sourceMessage.sourceDomain.toString() === destinationMessage.sourceDomain &&
        sourceMessage.destinationDomain.toString() === destinationMessage.destinationDomain &&
        // nonce is only set on destination
        (0, conversionUtils_1.addressOrBase58ToBytes32)(sourceMessage.sender.toString()) === (0, conversionUtils_1.addressOrBase58ToBytes32)(destinationMessage.sender) &&
        (0, conversionUtils_1.addressOrBase58ToBytes32)(sourceMessage.recipient.toString()) ===
            (0, conversionUtils_1.addressOrBase58ToBytes32)(destinationMessage.recipient) &&
        (0, conversionUtils_1.addressOrBase58ToBytes32)(sourceMessage.destinationCaller.toString()) ===
            (0, conversionUtils_1.addressOrBase58ToBytes32)(destinationMessage.destinationCaller) &&
        sourceMessage.minFinalityThreshold.toString() === destinationMessage.minFinalityThreshold &&
        // finalityThresholdExecuted is only set on destination
        (0, conversionUtils_1.addressOrBase58ToBytes32)(sourceMessage.messageBody.burnToken.toString()) ===
            (0, conversionUtils_1.addressOrBase58ToBytes32)(destinationMessage.decodedMessageBody.burnToken) &&
        (0, conversionUtils_1.addressOrBase58ToBytes32)(sourceMessage.messageBody.mintRecipient.toString()) ===
            (0, conversionUtils_1.addressOrBase58ToBytes32)(destinationMessage.decodedMessageBody.mintRecipient) &&
        sourceMessage.messageBody.amount.toString() === destinationMessage.decodedMessageBody.amount &&
        (0, conversionUtils_1.addressOrBase58ToBytes32)(sourceMessage.messageBody.messageSender.toString()) ===
            (0, conversionUtils_1.addressOrBase58ToBytes32)(destinationMessage.decodedMessageBody.messageSender) &&
        sourceMessage.messageBody.maxFee.toString() === destinationMessage.decodedMessageBody.maxFee &&
        // feeExecuted is only set on destination
        // expirationBlock is only set on destination
        sourceMessage.messageBody.hookData.equals(Buffer.from(ethers_1.ethers.utils.arrayify(destinationMessage.decodedMessageBody.hookData || "0x"))));
}
