"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AlgorandWormholeCore = void 0;
const connect_sdk_1 = require("@wormhole-foundation/connect-sdk");
const connect_sdk_algorand_1 = require("@wormhole-foundation/connect-sdk-algorand");
const algosdk_1 = require("algosdk");
const storage_1 = require("./storage");
class AlgorandWormholeCore {
    network;
    chain;
    connection;
    contracts;
    chainId;
    coreAppId;
    coreAppAddress;
    tokenBridgeAppId;
    tokenBridgeAppAddress;
    static MAX_SIGS_PER_TXN = 6;
    static ALGO_VERIFY_HASH = "EZATROXX2HISIRZDRGXW4LRQ46Z6IUJYYIHU3PJGP7P5IQDPKVX42N767A";
    static ALGO_VERIFY = new Uint8Array([
        6, 32, 4, 1, 0, 32, 20, 38, 1, 0, 49, 32, 50, 3, 18, 68, 49, 1, 35, 18, 68, 49, 16, 129, 6, 18,
        68, 54, 26, 1, 54, 26, 3, 54, 26, 2, 136, 0, 3, 68, 34, 67, 53, 2, 53, 1, 53, 0, 40, 53, 240,
        40, 53, 241, 52, 0, 21, 53, 5, 35, 53, 3, 35, 53, 4, 52, 3, 52, 5, 12, 65, 0, 68, 52, 1, 52, 0,
        52, 3, 129, 65, 8, 34, 88, 23, 52, 0, 52, 3, 34, 8, 36, 88, 52, 0, 52, 3, 129, 33, 8, 36, 88, 7,
        0, 53, 241, 53, 240, 52, 2, 52, 4, 37, 88, 52, 240, 52, 241, 80, 2, 87, 12, 20, 18, 68, 52, 3,
        129, 66, 8, 53, 3, 52, 4, 37, 8, 53, 4, 66, 255, 180, 34, 137,
    ]);
    // global state key for message fee
    static feeKey = connect_sdk_1.encoding.b64.encode("MessageFee");
    // method selector for verifying a VAA
    static verifyVaa = connect_sdk_1.encoding.bytes.encode("verifyVAA");
    // method selector for verifying signatures of a VAA
    static verifySigs = connect_sdk_1.encoding.bytes.encode("verifySigs");
    // method selector string for publishing a message
    static publishMessage = connect_sdk_1.encoding.bytes.encode("publishMessage");
    constructor(network, chain, connection, contracts) {
        this.network = network;
        this.chain = chain;
        this.connection = connection;
        this.contracts = contracts;
        this.chainId = (0, connect_sdk_1.toChainId)(chain);
        if (!contracts.coreBridge) {
            throw new Error(`Core contract address for chain ${chain} not found`);
        }
        const core = BigInt(contracts.coreBridge);
        this.coreAppId = core;
        this.coreAppAddress = (0, algosdk_1.getApplicationAddress)(core);
        if (!contracts.tokenBridge) {
            throw new Error(`TokenBridge contract address for chain ${chain} not found`);
        }
        const tokenBridge = BigInt(contracts.tokenBridge);
        this.tokenBridgeAppId = tokenBridge;
        this.tokenBridgeAppAddress = (0, algosdk_1.getApplicationAddress)(tokenBridge);
    }
    async *verifyMessage(sender, vaa, appId) {
        const address = new connect_sdk_algorand_1.AlgorandAddress(sender).toString();
        const txset = await AlgorandWormholeCore.submitVAAHeader(this.connection, this.coreAppId, appId ?? this.coreAppId, vaa, address);
        for (const tx of txset.txs) {
            yield this.createUnsignedTx(tx, "Core.verifyMessage");
        }
    }
    static async fromRpc(connection, config) {
        const [network, chain] = await connect_sdk_algorand_1.AlgorandPlatform.chainFromRpc(connection);
        const conf = config[chain];
        if (conf.network !== network)
            throw new Error(`Network mismatch: ${conf.network} !== ${network}`);
        return new AlgorandWormholeCore(network, chain, connection, conf.contracts);
    }
    async *publishMessage(sender, message) {
        // Call core bridge to publish message
        const _sender = new connect_sdk_algorand_1.AlgorandAddress(sender);
        const address = _sender.toString();
        const suggestedParams = await this.connection.getTransactionParams().do();
        const storage = storage_1.StorageLogicSig.forEmitter(this.coreAppId, _sender.toUint8Array());
        const { accounts, txs } = await AlgorandWormholeCore.maybeCreateStorageTx(this.connection, address, this.coreAppId, storage, suggestedParams);
        for (const tx of txs) {
            yield this.createUnsignedTx(tx, "Core.publishMessage", true);
        }
        const act = (0, algosdk_1.makeApplicationCallTxnFromObject)({
            from: address,
            appIndex: (0, connect_sdk_algorand_1.safeBigIntToNumber)(this.coreAppId),
            appArgs: [AlgorandWormholeCore.publishMessage, message, connect_sdk_1.encoding.bignum.toBytes(0n, 8)],
            accounts: accounts,
            onComplete: algosdk_1.OnApplicationComplete.NoOpOC,
            suggestedParams,
        });
        yield this.createUnsignedTx({ tx: act }, "Core.publishMessage", true);
    }
    /**
     * Return the message fee for the core bridge
     * @param client An Algodv2 client
     * @param bridgeId The application ID of the core bridge
     * @returns Promise with the message fee for the core bridge
     */
    async getMessageFee() {
        const applInfoResp = await this.connection
            .getApplicationByID((0, connect_sdk_algorand_1.safeBigIntToNumber)(this.coreAppId))
            .do();
        const appInfo = algosdk_1.modelsv2.Application.from_obj_for_encoding(applInfoResp);
        const val = appInfo.params.globalState?.find((kv) => kv.key === AlgorandWormholeCore.feeKey);
        return val ? BigInt(val.value.uint) : 0n;
    }
    async parseTransaction(txId) {
        const result = await this.connection.pendingTransactionInformation(txId).do();
        const ptr = algosdk_1.modelsv2.PendingTransactionResponse.from_obj_for_encoding(result);
        return this.parseTx(ptr);
    }
    parseTx(ptr) {
        const msgs = [];
        if (ptr.innerTxns && ptr.innerTxns.length > 0) {
            msgs.push(...ptr.innerTxns.flatMap((tx) => this.parseTx(tx)));
        }
        // Expect target is core app
        if (BigInt(ptr.txn.txn.apid ?? 0) !== this.coreAppId)
            return msgs;
        // Expect logs
        if (!ptr.logs || ptr.logs.length === 0)
            return msgs;
        // Expect publish messeage as first arg
        const args = ptr.txn.txn.apaa ?? [];
        if (args.length !== 3 ||
            !connect_sdk_1.encoding.bytes.equals(new Uint8Array(args[0]), AlgorandWormholeCore.publishMessage))
            return msgs;
        const sequence = connect_sdk_1.encoding.bignum.decode(ptr.logs[0]);
        const emitter = new connect_sdk_algorand_1.AlgorandAddress(ptr.txn.txn.snd).toUniversalAddress();
        msgs.push({ chain: this.chain, emitter, sequence });
        return msgs;
    }
    /**
     * Constructs opt in transactions
     * @param client An Algodv2 client
     * @param senderAddr Sender address
     * @param appId Application ID
     * @param storage StorageLogicSig
     * @returns Address and array of TransactionSignerPairs
     */
    static async maybeCreateStorageTx(client, senderAddr, appId, storage, suggestedParams) {
        const appAddr = (0, algosdk_1.getApplicationAddress)(appId);
        const storageAddress = storage.address();
        const txs = [];
        if (await storage_1.StorageLogicSig.storageAccountExists(client, storageAddress, appId))
            return { accounts: [storageAddress], txs };
        suggestedParams = suggestedParams ?? (await client.getTransactionParams().do());
        // Pay the storage account some ALGO to min balance requirements
        const seedTxn = (0, algosdk_1.makePaymentTxnWithSuggestedParamsFromObject)({
            from: senderAddr,
            to: storageAddress,
            amount: storage_1.SEED_AMT,
            suggestedParams,
        });
        seedTxn.fee = seedTxn.fee * 2;
        txs.push({ tx: seedTxn });
        // Opt in to the app and rekey to the app address that is using
        // this as storage
        const optinTxn = (0, algosdk_1.makeApplicationOptInTxnFromObject)({
            from: storageAddress,
            appIndex: (0, connect_sdk_algorand_1.safeBigIntToNumber)(appId),
            rekeyTo: appAddr,
            suggestedParams,
        });
        optinTxn.fee = 0;
        txs.push({
            tx: optinTxn,
            signer: {
                address: storage.address(),
                signTxn: (txn) => Promise.resolve((0, algosdk_1.signLogicSigTransaction)(txn, storage).blob),
            },
        });
        return {
            accounts: [storageAddress],
            txs,
        };
    }
    /**
     * Submits just the header of the VAA
     * @param client AlgodV2 client
     * @param bridgeId Application ID of the core bridge
     * @param vaa The VAA (just the header is used)
     * @param senderAddr Sending account address
     * @param appid Application ID
     * @returns Promise with current VAA state
     */
    static async submitVAAHeader(client, coreId, appid, vaa, senderAddr, suggestedParams) {
        suggestedParams = suggestedParams ?? (await client.getTransactionParams().do());
        let txs = [];
        // Get storage acct for message ID
        const msgStorage = storage_1.StorageLogicSig.forMessageId(appid, {
            chain: vaa.emitterChain,
            sequence: vaa.sequence,
            emitter: vaa.emitterAddress,
        });
        const { accounts: seqAddr, txs: seqOptInTxs } = await AlgorandWormholeCore.maybeCreateStorageTx(client, senderAddr, appid, msgStorage, suggestedParams);
        txs.push(...seqOptInTxs);
        // Get storage account for Guardian set
        const gsStorage = storage_1.StorageLogicSig.forGuardianSet(coreId, vaa.guardianSet);
        const { accounts: [storageAddr], txs: guardianOptInTxs, } = await AlgorandWormholeCore.maybeCreateStorageTx(client, senderAddr, coreId, gsStorage, suggestedParams);
        txs.push(...guardianOptInTxs);
        let accts = [...seqAddr, storageAddr];
        // Get the Guardian keys
        const keys = await storage_1.StorageLogicSig.decodeLocalState(client, coreId, storageAddr);
        // We don't pass the entire payload in but instead just pass it pre-digested.  This gets around size
        // limitations with lsigs AND reduces the cost of the entire operation on a congested network by reducing the
        // bytes passed into the transaction
        // This is a 2 pass digest
        const digest = (0, connect_sdk_1.keccak256)(vaa.hash);
        // How many signatures can we process in a single txn... we can do 6!
        // There are likely upwards of 19 signatures.  So, we ned to split things up
        const numSigs = vaa.signatures.length;
        const numTxns = Math.ceil(numSigs / AlgorandWormholeCore.MAX_SIGS_PER_TXN);
        const GuardianKeyLen = 20;
        const lsa = new algosdk_1.LogicSigAccount(AlgorandWormholeCore.ALGO_VERIFY);
        for (let nt = 0; nt < numTxns; nt++) {
            const step = nt * AlgorandWormholeCore.MAX_SIGS_PER_TXN;
            const sigs = vaa.signatures.slice(step, step + AlgorandWormholeCore.MAX_SIGS_PER_TXN);
            // The keyset is the set of Guardians that correspond
            // to the current set of signatures in this loop.
            // Each signature in 20 bytes and comes from decodeLocalState()
            const arraySize = sigs.length * GuardianKeyLen;
            const keySet = new Uint8Array(arraySize);
            for (let i = 0; i < sigs.length; i++) {
                // The first byte of the sig is the relative index of that signature in the signatures array
                // Use that index to get the appropriate Guardian key
                const sig = sigs[i];
                const key = keys.slice(sig.guardianIndex * GuardianKeyLen + 1, (sig.guardianIndex + 1) * GuardianKeyLen + 1);
                keySet.set(key, i * 20);
            }
            const appTxn = (0, algosdk_1.makeApplicationCallTxnFromObject)({
                appArgs: [
                    AlgorandWormholeCore.verifySigs,
                    connect_sdk_1.encoding.bytes.concat(...sigs.map((s) => connect_sdk_1.encoding.bytes.concat(new Uint8Array([s.guardianIndex]), s.signature.encode()))),
                    keySet,
                    digest,
                ],
                accounts: accts,
                appIndex: (0, connect_sdk_algorand_1.safeBigIntToNumber)(coreId),
                from: AlgorandWormholeCore.ALGO_VERIFY_HASH,
                onComplete: algosdk_1.OnApplicationComplete.NoOpOC,
                suggestedParams,
            });
            appTxn.fee = 0;
            txs.push({
                tx: appTxn,
                signer: {
                    address: lsa.address(),
                    signTxn: (txn) => Promise.resolve((0, algosdk_1.signLogicSigTransaction)(txn, lsa).blob),
                },
            });
        }
        const appTxn = (0, algosdk_1.makeApplicationCallTxnFromObject)({
            appArgs: [AlgorandWormholeCore.verifyVaa, (0, connect_sdk_1.serialize)(vaa)],
            accounts: accts,
            appIndex: (0, connect_sdk_algorand_1.safeBigIntToNumber)(coreId),
            from: senderAddr,
            onComplete: algosdk_1.OnApplicationComplete.NoOpOC,
            suggestedParams,
        });
        appTxn.fee = appTxn.fee * (2 + numTxns);
        txs.push({ tx: appTxn });
        return { accounts: accts, txs };
    }
    createUnsignedTx(txReq, description, parallelizable = true) {
        return new connect_sdk_algorand_1.AlgorandUnsignedTransaction(txReq, this.network, this.chain, description, parallelizable);
    }
}
exports.AlgorandWormholeCore = AlgorandWormholeCore;
//# sourceMappingURL=core.js.map