"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SolanaAutomaticTokenBridge = void 0;
const connect_sdk_1 = require("@wormhole-foundation/connect-sdk");
const connect_sdk_solana_1 = require("@wormhole-foundation/connect-sdk-solana");
const anchor_1 = require("@project-serum/anchor");
const web3_js_1 = require("@solana/web3.js");
const automaticTokenBridge_1 = require("./utils/automaticTokenBridge");
const spl_token_1 = require("@solana/spl-token");
require("@wormhole-foundation/connect-sdk-solana-core");
const consts_1 = require("./consts");
const SOL_DECIMALS = 9;
const TEN = new anchor_1.BN(10);
const SWAP_RATE_PRECISION = new anchor_1.BN(100000000);
class SolanaAutomaticTokenBridge {
    network;
    chain;
    connection;
    contracts;
    chainId;
    coreBridgeProgramId;
    tokenBridgeProgramId;
    tokenBridgeRelayer;
    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);
        const tokenBridgeRelayerAddress = contracts.tokenBridgeRelayer;
        if (!tokenBridgeRelayerAddress)
            throw new Error(`TokenBridge contract Address for chain ${chain} not found`);
        this.tokenBridgeRelayer = (0, automaticTokenBridge_1.createTokenBridgeRelayerProgramInterface)(tokenBridgeRelayerAddress, connection);
        this.tokenBridgeProgramId = new web3_js_1.PublicKey(contracts.tokenBridge);
        this.coreBridgeProgramId = new web3_js_1.PublicKey(contracts.coreBridge);
    }
    static async fromRpc(connection, config) {
        const [network, chain] = await connect_sdk_solana_1.SolanaPlatform.chainFromRpc(connection);
        const conf = config[chain];
        if (conf.network !== network)
            throw new Error(`Network mismatch for chain ${chain}: ${conf.network} != ${network}`);
        return new SolanaAutomaticTokenBridge(network, chain, connection, conf.contracts);
    }
    async *transfer(sender, recipient, token, amount, nativeGas) {
        const nonce = 0;
        const senderAddress = new connect_sdk_solana_1.SolanaAddress(sender).unwrap();
        const recipientAddress = recipient.address
            .toUniversalAddress()
            .toUint8Array();
        const tokenMint = this.mintAddress(token);
        const transaction = new web3_js_1.Transaction();
        if ((0, connect_sdk_1.isNative)(token)) {
            const ata = (0, spl_token_1.getAssociatedTokenAddressSync)(tokenMint, senderAddress);
            try {
                await (0, spl_token_1.getAccount)(this.connection, ata);
            }
            catch (e) {
                if (e instanceof spl_token_1.TokenAccountNotFoundError) {
                    // the relayer expects the wSOL associated token account to exist
                    const createAccountInst = (0, spl_token_1.createAssociatedTokenAccountInstruction)(senderAddress, ata, senderAddress, tokenMint);
                    transaction.add(createAccountInst);
                }
                else {
                    throw e;
                }
            }
        }
        const nativeGasAmount = nativeGas ? nativeGas : 0n;
        const tokenIsNative = (0, connect_sdk_1.isNative)(token);
        const transferIx = tokenIsNative
            ? await (0, automaticTokenBridge_1.createTransferNativeTokensWithRelayInstruction)(this.connection, this.tokenBridgeRelayer.programId, senderAddress, this.tokenBridgeProgramId, this.coreBridgeProgramId, tokenMint, amount, nativeGasAmount, recipientAddress, recipient.chain, nonce, tokenIsNative)
            : await (0, automaticTokenBridge_1.createTransferWrappedTokensWithRelayInstruction)(this.connection, this.tokenBridgeRelayer.programId, senderAddress, this.tokenBridgeProgramId, this.coreBridgeProgramId, tokenMint, amount, nativeGasAmount, recipientAddress, recipient.chain, nonce);
        transaction.add(transferIx);
        transaction.feePayer = senderAddress;
        yield this.createUnsignedTx({ transaction }, 'AutomaticTokenBridge.Transfer');
    }
    async *redeem(sender, vaa) {
        const transaction = new web3_js_1.Transaction();
        yield this.createUnsignedTx({ transaction }, 'AutomaticTokenBridge.Redeem');
        throw new Error('Method not implemented.');
    }
    async getRelayerFee(sender, recipient, token) {
        const tokenAddress = this.mintAddress(token);
        const [{ fee }, { swapRate }, { relayerFeePrecision }] = await Promise.all([
            this.getForeignContract(recipient.chain),
            this.getRegisteredToken(tokenAddress),
            this.getRedeemerConfig(),
        ]);
        const decimals = Number(await connect_sdk_solana_1.SolanaPlatform.getDecimals(this.chain, this.connection, token));
        const relayerFee = TEN.pow(new anchor_1.BN(decimals))
            .mul(fee)
            .mul(SWAP_RATE_PRECISION)
            .div(new anchor_1.BN(relayerFeePrecision).mul(swapRate));
        return BigInt(relayerFee.toString());
    }
    async maxSwapAmount(token) {
        const mint = this.mintAddress(token);
        const [{ swapRate, maxNativeSwapAmount }, { swapRate: solSwapRate }] = await Promise.all([
            this.getRegisteredToken(mint),
            this.getRegisteredToken(spl_token_1.NATIVE_MINT),
        ]);
        const decimals = Number(await connect_sdk_solana_1.SolanaPlatform.getDecimals(this.chain, this.connection, token));
        const nativeSwapRate = this.calculateNativeSwapRate(solSwapRate, swapRate);
        const maxSwapAmountIn = decimals > SOL_DECIMALS
            ? maxNativeSwapAmount
                .mul(nativeSwapRate)
                .mul(TEN.pow(new anchor_1.BN(decimals - SOL_DECIMALS)))
                .div(SWAP_RATE_PRECISION)
            : maxNativeSwapAmount
                .mul(nativeSwapRate)
                .div(TEN.pow(new anchor_1.BN(SOL_DECIMALS - decimals)).mul(SWAP_RATE_PRECISION));
        return BigInt(maxSwapAmountIn.toString());
    }
    async nativeTokenAmount(token, amount) {
        if (amount === 0n)
            return 0n;
        const mint = this.mintAddress(token);
        const decimals = Number(await connect_sdk_solana_1.SolanaPlatform.getDecimals(this.chain, this.connection, token));
        const [{ swapRate }, { swapRate: solSwapRate }] = await Promise.all([
            this.getRegisteredToken(mint),
            this.getRegisteredToken(spl_token_1.NATIVE_MINT),
        ]);
        const nativeSwapRate = this.calculateNativeSwapRate(solSwapRate, swapRate);
        const swapAmountOut = decimals > SOL_DECIMALS
            ? SWAP_RATE_PRECISION.mul(new anchor_1.BN(amount.toString())).div(nativeSwapRate.mul(TEN.pow(new anchor_1.BN(decimals - SOL_DECIMALS))))
            : SWAP_RATE_PRECISION.mul(new anchor_1.BN(amount.toString()))
                .mul(TEN.pow(new anchor_1.BN(SOL_DECIMALS - decimals)))
                .div(nativeSwapRate);
        return BigInt(swapAmountOut.toString());
    }
    async isRegisteredToken(token) {
        const mint = this.mintAddress(token);
        try {
            await this.getRegisteredToken(mint);
            return true;
        }
        catch (e) {
            if (e.message?.includes('Account does not exist')) {
                // the token is not registered
                return false;
            }
            throw e;
        }
    }
    mintAddress(token) {
        return (0, connect_sdk_1.isNative)(token)
            ? new web3_js_1.PublicKey(spl_token_1.NATIVE_MINT)
            : new connect_sdk_solana_1.SolanaAddress(token).unwrap();
    }
    async getRegisteredTokens() {
        return consts_1.registeredTokens[this.network].map((addr) => (0, connect_sdk_1.toNative)(this.chain, addr));
    }
    calculateNativeSwapRate(solSwapRate, swapRate) {
        return SWAP_RATE_PRECISION.mul(solSwapRate).div(swapRate);
    }
    async getForeignContract(chain) {
        return await this.tokenBridgeRelayer.account.foreignContract.fetch((0, automaticTokenBridge_1.deriveForeignContractAddress)(this.tokenBridgeRelayer.programId, chain));
    }
    async getRegisteredToken(mint) {
        return await this.tokenBridgeRelayer.account.registeredToken.fetch((0, automaticTokenBridge_1.deriveRegisteredTokenAddress)(this.tokenBridgeRelayer.programId, mint));
    }
    async getRedeemerConfig() {
        return await this.tokenBridgeRelayer.account.redeemerConfig.fetch((0, automaticTokenBridge_1.deriveRedeemerConfigAddress)(this.tokenBridgeRelayer.programId));
    }
    createUnsignedTx(txReq, description, parallelizable = false) {
        return new connect_sdk_solana_1.SolanaUnsignedTransaction(txReq, this.network, this.chain, description, parallelizable);
    }
}
exports.SolanaAutomaticTokenBridge = SolanaAutomaticTokenBridge;
//# sourceMappingURL=automaticTokenBridge.js.map