"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EvmPorticoBridge = void 0;
const connect_sdk_1 = require("@wormhole-foundation/connect-sdk");
const connect_sdk_evm_1 = require("@wormhole-foundation/connect-sdk-evm");
const ethers_1 = require("ethers");
const abis_1 = require("./abis");
const api_1 = require("./api");
const consts_1 = require("./consts");
const connect_sdk_evm_core_1 = require("@wormhole-foundation/connect-sdk-evm-core");
require("@wormhole-foundation/connect-sdk-evm-tokenbridge");
class EvmPorticoBridge {
    network;
    chain;
    provider;
    contracts;
    core;
    chainId;
    porticoAddress;
    uniswapAddress;
    porticoContract;
    uniswapContract;
    constructor(network, chain, provider, contracts, core) {
        this.network = network;
        this.chain = chain;
        this.provider = provider;
        this.contracts = contracts;
        this.core = core;
        if (!contracts.portico)
            throw new Error('Unsupported chain, no contract addresses for: ' + chain);
        const { portico: porticoAddress, uniswapQuoterV2: uniswapAddress } = contracts.portico;
        this.porticoAddress = porticoAddress;
        this.uniswapAddress = uniswapAddress;
        this.chainId = connect_sdk_1.nativeChainIds.networkChainToNativeChainId.get(network, chain);
        this.porticoContract = new ethers_1.ethers.Contract(this.porticoAddress, abis_1.porticoAbi.fragments, this.provider);
        this.uniswapContract = new ethers_1.ethers.Contract(this.uniswapAddress, abis_1.uniswapQuoterV2Abi.fragments, this.provider);
    }
    static async fromRpc(provider, config) {
        const [network, chain] = await connect_sdk_evm_1.EvmPlatform.chainFromRpc(provider);
        const core = await connect_sdk_evm_core_1.EvmWormholeCore.fromRpc(provider, config);
        const conf = config[chain];
        if (conf.network !== network)
            throw new Error(`Network mismatch: ${conf.network} != ${network}`);
        return new EvmPorticoBridge(network, chain, provider, conf.contracts, core);
    }
    async *transfer(sender, receiver, token, amount, destToken, quote) {
        const { minAmountStart, minAmountFinish } = quote.swapAmounts;
        if (minAmountStart === 0n)
            throw new Error('Invalid min swap amount');
        if (minAmountFinish === 0n)
            throw new Error('Invalid min swap amount');
        const senderAddress = new connect_sdk_evm_1.EvmAddress(sender).toString();
        const [isStartTokenNative, startToken] = (0, connect_sdk_1.resolveWrappedToken)(this.network, this.chain, token);
        const [isFinalTokenNative, finalToken] = (0, connect_sdk_1.resolveWrappedToken)(this.network, receiver.chain, destToken);
        const startTokenAddress = (0, connect_sdk_1.canonicalAddress)(startToken);
        const cannonTokenAddress = (0, connect_sdk_1.canonicalAddress)(this.getTransferrableToken(startTokenAddress));
        const receiverAddress = (0, connect_sdk_1.canonicalAddress)(receiver);
        const finalTokenAddress = (0, connect_sdk_1.canonicalAddress)(finalToken);
        const destinationPorticoAddress = connect_sdk_1.contracts.portico.get(this.network, receiver.chain).portico;
        const nonce = new Date().valueOf() % 2 ** 4;
        const flags = connect_sdk_1.PorticoBridge.serializeFlagSet({
            flags: {
                shouldWrapNative: isStartTokenNative,
                shouldUnwrapNative: isFinalTokenNative,
            },
            recipientChain: (0, connect_sdk_1.toChainId)(receiver.chain),
            bridgeNonce: nonce,
            feeTierStart: consts_1.FEE_TIER,
            feeTierFinish: consts_1.FEE_TIER,
            padding: new Uint8Array(19),
        });
        const transactionData = abis_1.porticoAbi.encodeFunctionData('start', [
            [
                flags,
                startTokenAddress.toLowerCase(),
                cannonTokenAddress,
                finalTokenAddress.toLowerCase(),
                receiverAddress,
                destinationPorticoAddress,
                amount.toString(),
                minAmountStart.toString(),
                minAmountFinish.toString(),
                quote.relayerFee.toString(),
            ],
        ]);
        // Approve the token if necessary
        if (!isStartTokenNative)
            yield* this.approve(startTokenAddress, senderAddress, amount, this.porticoAddress);
        const messageFee = await this.core.getMessageFee();
        const tx = {
            to: this.porticoAddress,
            data: transactionData,
            value: messageFee + (isStartTokenNative ? amount : 0n),
        };
        yield this.createUnsignedTransaction((0, connect_sdk_evm_1.addFrom)(tx, senderAddress), 'PorticoBridge.Transfer');
    }
    async *redeem(sender, vaa) {
        const txReq = await this.porticoContract
            .getFunction('receiveMessageAndSwap')
            .populateTransaction((0, connect_sdk_1.serialize)(vaa));
        const address = new connect_sdk_evm_1.EvmAddress(sender).toString();
        yield this.createUnsignedTransaction((0, connect_sdk_evm_1.addFrom)(txReq, address), 'PorticoBridge.Redeem');
    }
    async quoteSwap(input, output, amount) {
        const [, inputTokenId] = (0, connect_sdk_1.resolveWrappedToken)(this.network, this.chain, input);
        const [, outputTokenId] = (0, connect_sdk_1.resolveWrappedToken)(this.network, this.chain, output);
        const inputAddress = (0, connect_sdk_1.canonicalAddress)(inputTokenId);
        const outputAddress = (0, connect_sdk_1.canonicalAddress)(outputTokenId);
        if (connect_sdk_1.tokens.isEqualCaseInsensitive(inputAddress, outputAddress))
            return amount;
        const result = await this.uniswapContract
            .getFunction('quoteExactInputSingle')
            .staticCall([inputAddress, outputAddress, amount, consts_1.FEE_TIER, 0]);
        return result[0];
    }
    async quoteRelay(startToken, endToken) {
        return await api_1.PorticoApi.quoteRelayer(this.chain, startToken, endToken);
    }
    // For a given token, return the corresponding
    // Wormhole wrapped token that originated on Ethereum
    getTransferrableToken(address) {
        if (this.chain === 'Ethereum')
            return connect_sdk_1.Wormhole.chainAddress('Ethereum', address);
        // get the nativeTokenDetails
        const nToken = connect_sdk_1.tokens.getTokenByAddress(this.network, this.chain, address);
        if (!nToken)
            throw new Error('Unsupported source token: ' + address);
        // find the same token by symbol with an origin of Ethereum
        // this is the token we standardize for transfer
        const xToken = connect_sdk_1.tokens
            .getTokensBySymbol(this.network, this.chain, nToken.symbol)
            ?.find((orig) => {
            return orig.original === 'Ethereum';
        });
        if (!xToken)
            throw new Error(`Unsupported symbol for chain ${nToken.symbol}: ${this.chain} `);
        return connect_sdk_1.Wormhole.chainAddress(xToken.chain, xToken.address);
    }
    async *approve(token, senderAddr, amount, contract) {
        const tokenContract = connect_sdk_evm_1.EvmPlatform.getTokenImplementation(this.provider, token);
        const allowance = await tokenContract.allowance(senderAddr, contract);
        if (allowance < amount) {
            const txReq = await tokenContract.approve.populateTransaction(contract, amount);
            yield this.createUnsignedTransaction((0, connect_sdk_evm_1.addFrom)(txReq, senderAddr), 'PorticoBridge.Approve');
        }
    }
    createUnsignedTransaction(txReq, description) {
        return new connect_sdk_evm_1.EvmUnsignedTransaction((0, connect_sdk_evm_1.addChainId)(txReq, this.chainId), this.network, this.chain, description, false);
    }
}
exports.EvmPorticoBridge = EvmPorticoBridge;
//# sourceMappingURL=bridge.js.map