import { PorticoBridge, Wormhole, canonicalAddress, contracts, nativeChainIds, resolveWrappedToken, serialize, toChainId, tokens, } from '@wormhole-foundation/connect-sdk';
import { EvmAddress, EvmPlatform, EvmUnsignedTransaction, addChainId, addFrom, } from '@wormhole-foundation/connect-sdk-evm';
import { ethers } from 'ethers';
import { porticoAbi, uniswapQuoterV2Abi } from './abis';
import { PorticoApi } from './api';
import { FEE_TIER } from './consts';
import { EvmWormholeCore } from '@wormhole-foundation/connect-sdk-evm-core';
import '@wormhole-foundation/connect-sdk-evm-tokenbridge';
export 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 = nativeChainIds.networkChainToNativeChainId.get(network, chain);
        this.porticoContract = new ethers.Contract(this.porticoAddress, porticoAbi.fragments, this.provider);
        this.uniswapContract = new ethers.Contract(this.uniswapAddress, uniswapQuoterV2Abi.fragments, this.provider);
    }
    static async fromRpc(provider, config) {
        const [network, chain] = await EvmPlatform.chainFromRpc(provider);
        const core = await 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 EvmAddress(sender).toString();
        const [isStartTokenNative, startToken] = resolveWrappedToken(this.network, this.chain, token);
        const [isFinalTokenNative, finalToken] = resolveWrappedToken(this.network, receiver.chain, destToken);
        const startTokenAddress = canonicalAddress(startToken);
        const cannonTokenAddress = canonicalAddress(this.getTransferrableToken(startTokenAddress));
        const receiverAddress = canonicalAddress(receiver);
        const finalTokenAddress = canonicalAddress(finalToken);
        const destinationPorticoAddress = contracts.portico.get(this.network, receiver.chain).portico;
        const nonce = new Date().valueOf() % 2 ** 4;
        const flags = PorticoBridge.serializeFlagSet({
            flags: {
                shouldWrapNative: isStartTokenNative,
                shouldUnwrapNative: isFinalTokenNative,
            },
            recipientChain: toChainId(receiver.chain),
            bridgeNonce: nonce,
            feeTierStart: FEE_TIER,
            feeTierFinish: FEE_TIER,
            padding: new Uint8Array(19),
        });
        const transactionData = 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(addFrom(tx, senderAddress), 'PorticoBridge.Transfer');
    }
    async *redeem(sender, vaa) {
        const txReq = await this.porticoContract
            .getFunction('receiveMessageAndSwap')
            .populateTransaction(serialize(vaa));
        const address = new EvmAddress(sender).toString();
        yield this.createUnsignedTransaction(addFrom(txReq, address), 'PorticoBridge.Redeem');
    }
    async quoteSwap(input, output, amount) {
        const [, inputTokenId] = resolveWrappedToken(this.network, this.chain, input);
        const [, outputTokenId] = resolveWrappedToken(this.network, this.chain, output);
        const inputAddress = canonicalAddress(inputTokenId);
        const outputAddress = canonicalAddress(outputTokenId);
        if (tokens.isEqualCaseInsensitive(inputAddress, outputAddress))
            return amount;
        const result = await this.uniswapContract
            .getFunction('quoteExactInputSingle')
            .staticCall([inputAddress, outputAddress, amount, FEE_TIER, 0]);
        return result[0];
    }
    async quoteRelay(startToken, endToken) {
        return await 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 Wormhole.chainAddress('Ethereum', address);
        // get the nativeTokenDetails
        const nToken = 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 = 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 Wormhole.chainAddress(xToken.chain, xToken.address);
    }
    async *approve(token, senderAddr, amount, contract) {
        const tokenContract = 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(addFrom(txReq, senderAddr), 'PorticoBridge.Approve');
        }
    }
    createUnsignedTransaction(txReq, description) {
        return new EvmUnsignedTransaction(addChainId(txReq, this.chainId), this.network, this.chain, description, false);
    }
}
//# sourceMappingURL=bridge.js.map