import { uint8ArrayToHex } from "./array";
import { CHAIN_ID_ALEPHIUM } from "./consts";
import { keccak256 } from 'ethers/lib/utils';
import * as elliptic from "elliptic";
export const METADATA_REPLACE = new RegExp("\u0000", "g");
export const CoreModule = '00000000000000000000000000000000000000000000000000000000436f7265';
export const TokenBridgeModule = '000000000000000000000000000000000000000000546f6b656e427269646765';
export const NFTBridgeModule = '00000000000000000000000000000000000000000000004e4654427269646765';
export const TransferTokenPayloadId = 1;
export const AttestTokenPayloadId = 2;
export const TransferNFTPayloadId = 3;
export const CoreContractUpgradeActionId = 1;
export const GuardianSetUpgradeActionId = 2;
export const UpdateMessageFeeActionId = 3;
export const TransferFeeActionId = 4;
export const RegisterChainActionId = 1;
export const AppContractUpgradeActionId = 2;
export const DestroyUnexecutedSequencesActionId = 240;
export const UpdateMinimalConsistencyLevelActionId = 241;
export const UpdateRefundAddressActionId = 242;
export function isApplicationVAA(vaa) {
    return vaa.body.payload.type === 'AttestToken'
        || vaa.body.payload.type === 'TransferToken'
        || vaa.body.payload.type === 'TransferNFT';
}
export function isGovernanceVAA(vaa) {
    return !isApplicationVAA(vaa);
}
export class Signature {
    constructor(index, sig) {
        this.index = index;
        this.sig = sig;
    }
    static from(data) {
        if (data.length !== 66) {
            throw new Error(`Invalid signature ${uint8ArrayToHex(data)}, expected 66 bytes`);
        }
        return new Signature(data[0], data.slice(1));
    }
    serialize() {
        return Buffer.concat([Buffer.from([this.index]), this.sig]);
    }
}
function bytesToModule(bytes) {
    const hexStr = uint8ArrayToHex(bytes);
    switch (hexStr) {
        case CoreModule:
            return 'Core';
        case TokenBridgeModule:
            return 'TokenBridge';
        case NFTBridgeModule:
            return 'NFTBridge';
        default:
            throw new Error(`Invalid module: ${hexStr}`);
    }
}
class Reader {
    constructor(data) {
        this.offset = 0;
        this.buffer = Buffer.from(data);
    }
    updateOffset(size) {
        this.offset += size;
        if (this.offset > this.buffer.length) {
            throw new Error(`Read offset is out of range, offset: ${this.offset}, buffer size: ${this.buffer.length}`);
        }
    }
    readUInt8() {
        this.updateOffset(1);
        return this.buffer.readUInt8(this.offset - 1);
    }
    readUInt16BE() {
        this.updateOffset(2);
        return this.buffer.readUInt16BE(this.offset - 2);
    }
    readUInt32BE() {
        this.updateOffset(4);
        return this.buffer.readUInt32BE(this.offset - 4);
    }
    readUInt64BE() {
        this.updateOffset(8);
        return this.buffer.readBigUInt64BE(this.offset - 8);
    }
    readUInt256BE() {
        return BigInt('0x' + uint8ArrayToHex(this.readBytes(32)));
    }
    readModule() {
        return bytesToModule(this.readBytes(32));
    }
    readBytes(length) {
        this.updateOffset(length);
        return this.buffer.slice(this.offset - length, this.offset);
    }
    remain() {
        return this.buffer.slice(this.offset);
    }
}
class Writer {
    constructor(length) {
        this.offset = 0;
        this.buffer = Buffer.allocUnsafe(length);
    }
    updateOffset(size) {
        this.offset += size;
        if (this.offset > this.buffer.length) {
            throw new Error(`Write offset is out of range, offset: ${this.offset}, buffer size: ${this.buffer.length}`);
        }
    }
    writeUInt8(value) {
        this.updateOffset(1);
        this.buffer.writeUInt8(value, this.offset - 1);
    }
    writeUInt16BE(value) {
        this.updateOffset(2);
        this.buffer.writeUInt16BE(value, this.offset - 2);
    }
    writeUInt32BE(value) {
        this.updateOffset(4);
        this.buffer.writeUInt32BE(value, this.offset - 4);
    }
    writeUInt64BE(value) {
        this.updateOffset(8);
        this.buffer.writeBigUInt64BE(value, this.offset - 8);
    }
    writeUInt256BE(value) {
        const bytes = Buffer.from(value.toString(16).padStart(64, '0'), 'hex');
        this.writeBytes(bytes);
    }
    writeModule(module) {
        switch (module) {
            case 'Core':
                this.writeBytes(Buffer.from(CoreModule, 'hex'));
                break;
            case 'TokenBridge':
                this.writeBytes(Buffer.from(TokenBridgeModule, 'hex'));
                break;
            case 'NFTBridge':
                this.writeBytes(Buffer.from(NFTBridgeModule, 'hex'));
                break;
        }
    }
    writeBytes(data) {
        this.updateOffset(data.length);
        this.buffer.fill(data, this.offset - data.length, this.offset);
    }
    result() {
        if (this.offset === this.buffer.length) {
            return this.buffer;
        }
        throw new Error(`Write buffer is not full, offset: ${this.offset}, buffer size: ${this.buffer.length}`);
    }
}
export function extractBodyFromVAA(signedVAA) {
    const signatureSize = signedVAA[5];
    const offset = 6 + signatureSize * 66;
    return signedVAA.slice(offset);
}
export function extractPayloadFromVAA(signedVAA) {
    const signatureSize = signedVAA[5];
    const offset = 6 + signatureSize * 66 + 53;
    return signedVAA.slice(offset);
}
export function extractSequenceFromVAA(signedVAA) {
    const signatureSize = signedVAA[5];
    const offset = 6 + signatureSize * 66 + 44;
    return Buffer.from(signedVAA.slice(offset, offset + 8)).readBigUInt64BE();
}
function _serializeVAABody(body, payloadSerializer) {
    const writer = new Writer(53);
    writer.writeUInt32BE(body.timestamp);
    writer.writeUInt32BE(body.nonce);
    writer.writeUInt16BE(body.emitterChainId);
    writer.writeUInt16BE(body.targetChainId);
    writer.writeBytes(body.emitterAddress);
    writer.writeUInt64BE(body.sequence);
    writer.writeUInt8(body.consistencyLevel);
    return Buffer.concat([writer.result(), payloadSerializer(body.payload)]);
}
function _serializeVAA(vaa, payloadSerializer) {
    const writer = new Writer(6);
    writer.writeUInt8(vaa.version);
    writer.writeUInt32BE(vaa.guardianSetIndex);
    writer.writeUInt8(vaa.signatures.length);
    const signatures = Buffer.concat(vaa.signatures.map(sig => sig.serialize()));
    return Buffer.concat([writer.result(), signatures, _serializeVAABody(vaa.body, payloadSerializer)]);
}
function _deserializeVAABody(data, payloadDeserializer) {
    const reader = new Reader(data);
    return {
        timestamp: reader.readUInt32BE(),
        nonce: reader.readUInt32BE(),
        emitterChainId: reader.readUInt16BE(),
        targetChainId: reader.readUInt16BE(),
        emitterAddress: reader.readBytes(32),
        sequence: reader.readUInt64BE(),
        consistencyLevel: reader.readUInt8(),
        payload: payloadDeserializer(reader.remain())
    };
}
function _deserializeVAA(data, payloadDeserializer) {
    const reader = new Reader(data);
    const version = reader.readUInt8();
    const guardianSetIndex = reader.readUInt32BE();
    const signatureSize = reader.readUInt8();
    if (signatureSize === 0) {
        throw new Error(`Invalid signature size ${signatureSize}`);
    }
    const signatures = Array.from(Array(signatureSize).keys()).map(() => {
        return Signature.from(reader.readBytes(66));
    });
    const body = _deserializeVAABody(reader.remain(), payloadDeserializer);
    return {
        version,
        guardianSetIndex,
        signatures,
        body
    };
}
function validatePayloadSize(payload, expected, type) {
    if (payload.length !== expected) {
        throw new Error(`Invalid ${type} payload ${uint8ArrayToHex(payload)}, expected ${expected} bytes`);
    }
}
function validatePayloadId(payloadId, expected, type) {
    if (payloadId !== expected) {
        throw new Error(`Invalid ${type} payload id ${payloadId}, expected ${expected}`);
    }
}
function validateModule(module, expected, type) {
    if (module !== expected) {
        throw new Error(`Invalid ${type} payload module ${module}, expected ${expected}`);
    }
}
export function deserializeTransferTokenPayload(payload) {
    const reader = new Reader(payload);
    validatePayloadId(reader.readUInt8(), TransferTokenPayloadId, 'TransferToken');
    const amount = reader.readUInt256BE();
    const originAddress = reader.readBytes(32);
    const originChain = reader.readUInt16BE();
    const targetAddressSize = reader.readUInt16BE();
    const targetAddress = reader.readBytes(targetAddressSize);
    const fee = reader.readUInt256BE();
    validatePayloadSize(payload, 101 + targetAddressSize, 'TransferToken');
    return {
        type: 'TransferToken',
        amount,
        originAddress,
        originChain,
        targetAddress,
        fee
    };
}
export function serializeTransferTokenPayload(payload) {
    const writer = new Writer(101 + payload.targetAddress.length);
    writer.writeUInt8(TransferTokenPayloadId); // payloadId
    writer.writeUInt256BE(payload.amount);
    writer.writeBytes(payload.originAddress);
    writer.writeUInt16BE(payload.originChain);
    writer.writeUInt16BE(payload.targetAddress.length);
    writer.writeBytes(payload.targetAddress);
    writer.writeUInt256BE(payload.fee);
    return writer.result();
}
export function deserializeTransferTokenVAA(data) {
    return _deserializeVAA(data, deserializeTransferTokenPayload);
}
export function serializeTransferTokenVAA(vaa) {
    return _serializeVAA(vaa, serializeTransferTokenPayload);
}
export function bytes32ToUtf8String(bytes) {
    return Buffer.from(bytes).toString('utf8').replace(METADATA_REPLACE, '');
}
function utf8StringTo32Bytes(str) {
    const buf = Buffer.from(str, 'utf8');
    if (buf.length > 32) {
        throw new Error(`String ${str} exceed 32 bytes`);
    }
    const prefix = Buffer.alloc(32 - buf.length);
    return Buffer.concat([prefix, buf]);
}
export function deserializeTransferNFTPayload(payload) {
    const reader = new Reader(payload);
    validatePayloadId(reader.readUInt8(), TransferNFTPayloadId, 'TransferNFT');
    const transferNFT = {
        type: 'TransferNFT',
        originAddress: reader.readBytes(32),
        originChain: reader.readUInt16BE(),
        symbol: bytes32ToUtf8String(reader.readBytes(32)),
        name: bytes32ToUtf8String(reader.readBytes(32)),
        tokenId: reader.readUInt256BE(),
        uri: reader.readBytes(reader.readUInt8()),
        targetAddress: reader.readBytes(32)
    };
    if (reader.remain().length !== 0) {
        throw new Error('Invalid size of transfer NFT VAA');
    }
    return transferNFT;
}
export function deserializeTransferNFTVAA(data) {
    return _deserializeVAA(data, deserializeTransferNFTPayload);
}
export function serializeTransferNFTPayload(payload) {
    const writer = new Writer(164 + payload.uri.length);
    writer.writeUInt8(TransferNFTPayloadId); // payload id
    writer.writeBytes(payload.originAddress);
    writer.writeUInt16BE(payload.originChain);
    writer.writeBytes(utf8StringTo32Bytes(payload.symbol));
    writer.writeBytes(utf8StringTo32Bytes(payload.name));
    writer.writeUInt256BE(payload.tokenId);
    writer.writeUInt8(payload.uri.length);
    writer.writeBytes(payload.uri);
    writer.writeBytes(payload.targetAddress);
    return writer.result();
}
export function serializeTransferNFTVAA(vaa) {
    return _serializeVAA(vaa, serializeTransferNFTPayload);
}
export function deserializeAttestTokenPayload(payload) {
    validatePayloadSize(payload, 100, 'AttestToken');
    const reader = new Reader(payload);
    validatePayloadId(reader.readUInt8(), AttestTokenPayloadId, 'AttestToken');
    return {
        type: 'AttestToken',
        tokenId: reader.readBytes(32),
        tokenChainId: reader.readUInt16BE(),
        decimals: reader.readUInt8(),
        symbol: bytes32ToUtf8String(reader.readBytes(32)),
        name: bytes32ToUtf8String(reader.readBytes(32))
    };
}
export function deserializeAttestTokenVAA(data) {
    return _deserializeVAA(data, deserializeAttestTokenPayload);
}
export function serializeAttestTokenPayload(payload) {
    const writer = new Writer(100);
    writer.writeUInt8(AttestTokenPayloadId); // payload id
    writer.writeBytes(payload.tokenId);
    writer.writeUInt16BE(payload.tokenChainId);
    writer.writeUInt8(payload.decimals);
    writer.writeBytes(utf8StringTo32Bytes(payload.symbol));
    writer.writeBytes(utf8StringTo32Bytes(payload.name));
    return writer.result();
}
export function serializeAttestTokenVAA(vaa) {
    return _serializeVAA(vaa, serializeAttestTokenPayload);
}
function getContractUpgradeActionIdByModule(module) {
    return module === 'Core' ? CoreContractUpgradeActionId : AppContractUpgradeActionId;
}
export function deserializeContractUpgradePayload(data) {
    validatePayloadSize(data, 65, 'ContractUpgrade');
    const reader = new Reader(data);
    const module = reader.readModule();
    const actionId = getContractUpgradeActionIdByModule(module);
    validatePayloadId(reader.readUInt8(), actionId, 'ContractUpgrade');
    const newContractAddress = reader.readBytes(32);
    return {
        type: 'ContractUpgrade',
        module,
        newContractAddress
    };
}
export function deserializeCoreContractUpgradeVAA(data) {
    return _deserializeVAA(data, (payload) => {
        const decoded = deserializeContractUpgradePayload(payload);
        validateModule(decoded.module, 'Core', 'ContractUpgrade');
        return decoded;
    });
}
export function deserializeTokenBridgeContractUpgradeVAA(data) {
    return _deserializeVAA(data, (payload) => {
        const decoded = deserializeContractUpgradePayload(payload);
        validateModule(decoded.module, 'TokenBridge', 'ContractUpgrade');
        return decoded;
    });
}
export function deserializeNFTBridgeContractUpgradeVAA(data) {
    return _deserializeVAA(data, (payload) => {
        const decoded = deserializeContractUpgradePayload(payload);
        validateModule(decoded.module, 'NFTBridge', 'ContractUpgrade');
        return decoded;
    });
}
export function serializeContractUpgradePayload(payload) {
    const writer = new Writer(65);
    writer.writeModule(payload.module);
    const actionId = getContractUpgradeActionIdByModule(payload.module);
    writer.writeUInt8(actionId);
    writer.writeBytes(payload.newContractAddress);
    return writer.result();
}
export function serializeContractUpgradeVAA(vaa) {
    return _serializeVAA(vaa, serializeContractUpgradePayload);
}
export function deserializeGuardianSetUpgradePayload(data) {
    const reader = new Reader(data);
    validateModule(reader.readModule(), 'Core', 'GuardianSetUpgrade');
    validatePayloadId(reader.readUInt8(), GuardianSetUpgradeActionId, 'GuardianSetUpgrade');
    const newGuardianSetIndex = reader.readUInt32BE();
    const newGuardianSetSize = reader.readUInt8();
    const newGuardianSet = Array.from(Array(newGuardianSetSize).keys()).map(() => {
        return reader.readBytes(20);
    });
    validatePayloadSize(data, 38 + newGuardianSet.length * 20, 'GuardianSetUpgrade');
    return {
        type: 'GuardianSetUpgrade',
        module: 'Core',
        newGuardianSetIndex,
        newGuardianSet
    };
}
export function deserializeGuardianSetUpgradeVAA(data) {
    return _deserializeVAA(data, deserializeGuardianSetUpgradePayload);
}
export function serializeGuardianSetUpgradePayload(payload) {
    const writer = new Writer(38 + payload.newGuardianSet.length * 20);
    writer.writeModule('Core');
    writer.writeUInt8(GuardianSetUpgradeActionId); // action id
    writer.writeUInt32BE(payload.newGuardianSetIndex);
    writer.writeUInt8(payload.newGuardianSet.length);
    payload.newGuardianSet.forEach(guardian => {
        writer.writeBytes(guardian);
    });
    return writer.result();
}
export function serializeGuardianSetUpgradeVAA(vaa) {
    return _serializeVAA(vaa, serializeGuardianSetUpgradePayload);
}
export function deserializeUpdateMessageFeePayload(data) {
    validatePayloadSize(data, 65, 'UpdateMessageFee');
    const reader = new Reader(data);
    validateModule(reader.readModule(), 'Core', 'UpdateMessageFee');
    validatePayloadId(reader.readUInt8(), UpdateMessageFeeActionId, 'UpdateMessageFee');
    const newMessageFee = reader.readUInt256BE();
    return {
        type: 'UpdateMessageFee',
        module: 'Core',
        newMessageFee
    };
}
export function deserializeUpdateMessageFeeVAA(data) {
    return _deserializeVAA(data, deserializeUpdateMessageFeePayload);
}
export function serializeUpdateMessageFeePayload(payload) {
    const writer = new Writer(65);
    writer.writeModule('Core');
    writer.writeUInt8(UpdateMessageFeeActionId); // action id
    writer.writeUInt256BE(payload.newMessageFee);
    return writer.result();
}
export function serializeUpdateMessageFeeVAA(vaa) {
    return _serializeVAA(vaa, serializeUpdateMessageFeePayload);
}
export function deserializeTransferFeePayload(data) {
    validatePayloadSize(data, 97, 'TransferFee');
    const reader = new Reader(data);
    validateModule(reader.readModule(), 'Core', 'TransferFee');
    validatePayloadId(reader.readUInt8(), TransferFeeActionId, 'TransferFee');
    return {
        type: 'TransferFee',
        module: 'Core',
        amount: reader.readUInt256BE(),
        recipient: reader.readBytes(32)
    };
}
export function deserializeTransferFeeVAA(data) {
    return _deserializeVAA(data, deserializeTransferFeePayload);
}
export function serializeTransferFeePayload(payload) {
    const writer = new Writer(97);
    writer.writeModule('Core');
    writer.writeUInt8(TransferFeeActionId); // action id
    writer.writeUInt256BE(payload.amount);
    writer.writeBytes(payload.recipient);
    return writer.result();
}
export function serializeTransferFeeVAA(vaa) {
    return _serializeVAA(vaa, serializeTransferFeePayload);
}
export function deserializeAlphContractUpgradePayload(data) {
    const reader = new Reader(data);
    const module = reader.readModule();
    if (module === 'NFTBridge') {
        throw new Error('NFTBridge does not supported in Alephium now');
    }
    const actionId = getContractUpgradeActionIdByModule(module);
    validatePayloadId(reader.readUInt8(), actionId, 'AlphContractUpgrade');
    const codeLength = reader.readUInt16BE();
    const newCode = reader.readBytes(codeLength);
    if (35 + codeLength === data.length) {
        return {
            type: 'AlphContractUpgrade',
            module,
            newCode
        };
    }
    const prevStateHash = reader.readBytes(32);
    const newStateLength = reader.readUInt16BE();
    const newState = reader.readBytes(newStateLength);
    validatePayloadSize(data, 35 + codeLength + 32 + 2 + newStateLength, 'AlphContractUpgrade');
    return {
        type: 'AlphContractUpgrade',
        module,
        newCode,
        prevStateHash,
        newState
    };
}
export function deserializeAlphCoreContractUpgradeVAA(data) {
    return _deserializeVAA(data, (payload) => {
        const decoded = deserializeAlphContractUpgradePayload(payload);
        validateModule(decoded.module, 'Core', 'AlphContractUpgrade');
        return decoded;
    });
}
export function deserializeAlphTokenBridgeContractUpgradeVAA(data) {
    return _deserializeVAA(data, (payload) => {
        const decoded = deserializeAlphContractUpgradePayload(payload);
        validateModule(decoded.module, 'TokenBridge', 'AlphContractUpgrade');
        return decoded;
    });
}
export function serializeAlphContractUpgradePayload(payload) {
    const writer0 = new Writer(35);
    writer0.writeModule(payload.module);
    writer0.writeUInt8(getContractUpgradeActionIdByModule(payload.module));
    writer0.writeUInt16BE(payload.newCode.length);
    const buffer0 = Buffer.concat([writer0.result(), payload.newCode]);
    if (payload.prevStateHash === undefined && payload.newState === undefined) {
        return buffer0;
    }
    if (payload.prevStateHash === undefined || payload.newState === undefined) {
        throw new Error('Invalid alephium contract upgrade payload');
    }
    const writer1 = new Writer(34);
    writer1.writeBytes(payload.prevStateHash);
    writer1.writeUInt16BE(payload.newState.length);
    return Buffer.concat([buffer0, writer1.result(), payload.newState]);
}
export function serializeAlphContractUpgradeVAA(vaa) {
    return _serializeVAA(vaa, serializeAlphContractUpgradePayload);
}
export function deserializeRegisterChainPayload(payload) {
    validatePayloadSize(payload, 67, 'RegisterChain');
    const reader = new Reader(payload);
    const module = reader.readModule();
    if (module === 'Core') {
        throw new Error('Invalid RegisterChain module: Core');
    }
    validatePayloadId(reader.readUInt8(), RegisterChainActionId, 'RegisterChain');
    const emitterChainId = reader.readUInt16BE();
    const emitterAddress = reader.readBytes(32);
    return {
        type: 'RegisterChain',
        module,
        emitterChainId,
        emitterAddress
    };
}
export function deserializeTokenBridgeRegisterChainVAA(data) {
    return _deserializeVAA(data, (payload) => {
        const decoded = deserializeRegisterChainPayload(payload);
        validateModule(decoded.module, 'TokenBridge', 'RegisterChain');
        return decoded;
    });
}
export function deserializeNFTBridgeRegisterChainVAA(data) {
    return _deserializeVAA(data, (payload) => {
        const decoded = deserializeRegisterChainPayload(payload);
        validateModule(decoded.module, 'NFTBridge', 'RegisterChain');
        return decoded;
    });
}
export function serializeRegisterChainPayload(payload) {
    const writer = new Writer(67);
    writer.writeModule(payload.module);
    writer.writeUInt8(RegisterChainActionId); // action id
    writer.writeUInt16BE(payload.emitterChainId);
    writer.writeBytes(payload.emitterAddress);
    return writer.result();
}
export function serializeRegisterChainVAA(vaa) {
    return _serializeVAA(vaa, serializeRegisterChainPayload);
}
export function deserializeDestroyUnexecutedSequencesPayload(payload) {
    const reader = new Reader(payload);
    validateModule(reader.readModule(), 'TokenBridge', 'DestroyUnexecutedSequences');
    validatePayloadId(reader.readUInt8(), DestroyUnexecutedSequencesActionId, 'DestroyUnexecutedSequences');
    const emitterChainId = reader.readUInt16BE();
    const size = reader.readUInt16BE();
    validatePayloadSize(payload, 37 + size * 8, 'DestroyUnexecutedSequences');
    const indexes = Array.from(Array(size).keys()).map(() => {
        return reader.readUInt64BE();
    });
    return {
        type: 'DestroyUnexecutedSequences',
        module: 'TokenBridge',
        emitterChainId,
        indexes
    };
}
export function deserializeDestroyUnexecutedSequencesVAA(data) {
    return _deserializeVAA(data, deserializeDestroyUnexecutedSequencesPayload);
}
export function serializeDestroyUnexecutedSequencesPayload(payload) {
    const writer = new Writer(37 + payload.indexes.length * 8);
    writer.writeModule('TokenBridge');
    writer.writeUInt8(DestroyUnexecutedSequencesActionId); // action id
    writer.writeUInt16BE(payload.emitterChainId);
    writer.writeUInt16BE(payload.indexes.length);
    payload.indexes.forEach(index => {
        writer.writeUInt64BE(index);
    });
    return writer.result();
}
export function serializeDestroyUnexecutedSequencesVAA(vaa) {
    return _serializeVAA(vaa, serializeDestroyUnexecutedSequencesPayload);
}
export function deserializeUpdateMinimalConsistencyLevelPayload(payload) {
    validatePayloadSize(payload, 34, 'UpdateMinimalConsistencyLevel');
    const reader = new Reader(payload);
    validateModule(reader.readModule(), 'TokenBridge', 'UpdateMinimalConsistencyLevel');
    validatePayloadId(reader.readUInt8(), UpdateMinimalConsistencyLevelActionId, 'UpdateMinimalConsistencyLevel');
    const newConsistencyLevel = reader.readUInt8();
    return {
        type: 'UpdateMinimalConsistencyLevel',
        module: 'TokenBridge',
        newConsistencyLevel
    };
}
export function deserializeUpdateMinimalConsistencyLevelVAA(data) {
    return _deserializeVAA(data, deserializeUpdateMinimalConsistencyLevelPayload);
}
export function serializeUpdateMinimalConsistencyLevelPayload(payload) {
    const writer = new Writer(34);
    writer.writeModule('TokenBridge');
    writer.writeUInt8(UpdateMinimalConsistencyLevelActionId);
    writer.writeUInt8(payload.newConsistencyLevel);
    return writer.result();
}
export function serializeUpdateMinimalConsistencyLevelVAA(vaa) {
    return _serializeVAA(vaa, serializeUpdateMinimalConsistencyLevelPayload);
}
export function deserializeUpdateRefundAddressPayload(payload) {
    const reader = new Reader(payload);
    validateModule(reader.readModule(), 'TokenBridge', 'UpdateRefundAddress');
    validatePayloadId(reader.readUInt8(), UpdateRefundAddressActionId, 'UpdateRefundAddress');
    const size = reader.readUInt16BE();
    validatePayloadSize(payload, 35 + size, 'UpdateRefundAddress');
    const newRefundAddress = reader.readBytes(size);
    return {
        type: 'UpdateRefundAddress',
        module: 'TokenBridge',
        newRefundAddress
    };
}
export function deserializeUpdateRefundAddressVAA(data) {
    return _deserializeVAA(data, deserializeUpdateRefundAddressPayload);
}
export function serializeUpdateRefundAddressPayload(payload) {
    const writer = new Writer(35);
    writer.writeModule('TokenBridge');
    writer.writeUInt8(UpdateRefundAddressActionId);
    writer.writeUInt16BE(payload.newRefundAddress.length);
    return Buffer.concat([writer.result(), payload.newRefundAddress]);
}
export function serializeUpdateRefundAddressVAA(vaa) {
    return _serializeVAA(vaa, serializeUpdateRefundAddressPayload);
}
function deserializeApplicationVAA(signedVAA, payloadId) {
    switch (payloadId) {
        case TransferTokenPayloadId:
            return deserializeTransferTokenVAA(signedVAA);
        case AttestTokenPayloadId:
            return deserializeAttestTokenVAA(signedVAA);
        case TransferNFTPayloadId:
            return deserializeTransferNFTVAA(signedVAA);
        default:
            throw new Error(`Invalid application vaa payload id: ${payloadId}`);
    }
}
function deserializeCoreGovernanceVAA(signedVAA, actionId, targetChainId) {
    switch (actionId) {
        case CoreContractUpgradeActionId:
            if (targetChainId === CHAIN_ID_ALEPHIUM) {
                return deserializeAlphCoreContractUpgradeVAA(signedVAA);
            }
            return deserializeCoreContractUpgradeVAA(signedVAA);
        case GuardianSetUpgradeActionId:
            return deserializeGuardianSetUpgradeVAA(signedVAA);
        case UpdateMessageFeeActionId:
            return deserializeUpdateMessageFeeVAA(signedVAA);
        case TransferFeeActionId:
            return deserializeTransferFeeVAA(signedVAA);
        default:
            throw new Error(`Invalid CoreGovernancePayload action id ${actionId}`);
    }
}
function deserializeTokenBridgeGovernanceVAA(signedVAA, actionId, targetChainId) {
    switch (actionId) {
        case RegisterChainActionId:
            return deserializeTokenBridgeRegisterChainVAA(signedVAA);
        case AppContractUpgradeActionId:
            if (targetChainId === CHAIN_ID_ALEPHIUM) {
                return deserializeAlphTokenBridgeContractUpgradeVAA(signedVAA);
            }
            return deserializeTokenBridgeContractUpgradeVAA(signedVAA);
        case DestroyUnexecutedSequencesActionId:
            return deserializeDestroyUnexecutedSequencesVAA(signedVAA);
        case UpdateMinimalConsistencyLevelActionId:
            return deserializeUpdateMinimalConsistencyLevelVAA(signedVAA);
        case UpdateRefundAddressActionId:
            return deserializeUpdateRefundAddressVAA(signedVAA);
        default:
            throw new Error(`Invalid TokenBridgeGovernancePayload action id ${actionId}`);
    }
}
function deserializeNFTBridgeGovernanceVAA(signedVAA, actionId) {
    switch (actionId) {
        case RegisterChainActionId:
            return deserializeNFTBridgeRegisterChainVAA(signedVAA);
        case AppContractUpgradeActionId:
            return deserializeNFTBridgeContractUpgradeVAA(signedVAA);
        default:
            throw new Error(`Invalid NFTBridgeGovernancePayload action id ${actionId}`);
    }
}
function deserializeGovernanceVAA(signedVAA, module, actionId, targetChainId) {
    switch (module) {
        case 'Core':
            return deserializeCoreGovernanceVAA(signedVAA, actionId, targetChainId);
        case 'TokenBridge':
            return deserializeTokenBridgeGovernanceVAA(signedVAA, actionId, targetChainId);
        case 'NFTBridge':
            return deserializeNFTBridgeGovernanceVAA(signedVAA, actionId);
    }
}
export function deserializeVAA(signedVAA) {
    const body = extractBodyFromVAA(signedVAA);
    const payload = body.slice(53);
    const payloadId = payload[0];
    if (payloadId !== 0) {
        return deserializeApplicationVAA(signedVAA, payloadId);
    }
    const module = bytesToModule(payload.slice(0, 32));
    const actionId = payload[32];
    const targetChainId = Buffer.from(body).readUInt16BE(10);
    return deserializeGovernanceVAA(signedVAA, module, actionId, targetChainId);
}
export const VAAPayloadSerializers = {
    'ContractUpgrade': serializeContractUpgradePayload,
    'GuardianSetUpgrade': serializeGuardianSetUpgradePayload,
    'UpdateMessageFee': serializeUpdateMessageFeePayload,
    'TransferFee': serializeTransferFeePayload,
    'AlphContractUpgrade': serializeAlphContractUpgradePayload,
    'RegisterChain': serializeRegisterChainPayload,
    'DestroyUnexecutedSequences': serializeDestroyUnexecutedSequencesPayload,
    'UpdateMinimalConsistencyLevel': serializeUpdateMinimalConsistencyLevelPayload,
    'UpdateRefundAddress': serializeUpdateRefundAddressPayload,
    'AttestToken': serializeAttestTokenPayload,
    'TransferToken': serializeTransferTokenPayload,
    'TransferNFT': serializeTransferNFTPayload
};
export const VAAPayloadDeserializers = {
    'ContractUpgrade': deserializeContractUpgradePayload,
    'GuardianSetUpgrade': deserializeGuardianSetUpgradePayload,
    'UpdateMessageFee': deserializeUpdateMessageFeePayload,
    'TransferFee': deserializeTransferFeePayload,
    'AlphContractUpgrade': deserializeAlphContractUpgradePayload,
    'RegisterChain': deserializeRegisterChainPayload,
    'DestroyUnexecutedSequences': deserializeDestroyUnexecutedSequencesPayload,
    'UpdateMinimalConsistencyLevel': deserializeUpdateMinimalConsistencyLevelPayload,
    'UpdateRefundAddress': deserializeUpdateRefundAddressPayload,
    'AttestToken': deserializeAttestTokenPayload,
    'TransferToken': deserializeTransferTokenPayload,
    'TransferNFT': deserializeTransferNFTPayload
};
export function serializeVAABody(body) {
    const serializer = VAAPayloadSerializers[body.payload.type];
    return _serializeVAABody(body, serializer);
}
export function serializeVAA(vaa) {
    const serializer = VAAPayloadSerializers[vaa.body.payload.type];
    return _serializeVAA(vaa, serializer);
}
export function signVAABody(keys, body) {
    const hash = keccak256(keccak256(serializeVAABody(body))).slice(2);
    const ec = new elliptic.ec("secp256k1");
    return keys.map((k, index) => {
        const key = ec.keyFromPrivate(k);
        const sig = key.sign(hash, { canonical: true });
        const signature = [
            sig.r.toString(16).padStart(64, '0'),
            sig.s.toString(16).padStart(64, '0'),
            sig.recoveryParam.toString(16).padStart(2, '0')
        ].join('');
        return new Signature(index, Buffer.from(signature, 'hex'));
    });
}
