import { isEthAddress, } from "@equilab/utils";
import BigNumber from "bignumber.js";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useEthereum } from "../../../contexts/ethereum";
import { useBase } from "../../../contexts/substrate/base";
import { Bridge, ERC20, WETH } from "./abi";
const serializeTokenAmount = (amount, decimals) => new BigNumber(10).pow(decimals).times(amount).toString(10);
const serializeDepositPayload = (amount, decimals, address, { hex, dec, validate }) => {
    if (!validate(address)) {
        throw new Error(`invalid polkadot address [${address}]`);
    }
    const account = hex(dec(address), -1, false);
    return ("0x" +
        // amount: 64 char length
        new BigNumber(10)
            .pow(decimals ?? 0)
            .times(amount)
            .toString(16)
            .padStart(64, "0") +
        // encoded polkadot address length(divided by 2): 64 char length
        (account.length / 2).toString(16).padStart(64, "0") +
        // encoded polkadot address
        account);
};
export const useERC20Deposit = ({ bridgeAddress, tokenAddress, spender, dest, resourceId, }) => {
    const { util, utilCrypto, validateSubstrateAddress } = useBase();
    const { address, web3, chainId } = useEthereum();
    const [allowance, setAllowance] = useState();
    const [balance, setBalance] = useState();
    const [bridgeBalance, setBridgeBalance] = useState();
    const [symbol, setSymbol] = useState();
    const [decimals, setDecimals] = useState();
    useEffect(() => {
        if (!isEthAddress(spender)) {
            console.warn(`incorrect spender [${spender}]`);
        }
    }, [spender]);
    const bridgeContract = useMemo(() => {
        if (isEthAddress(bridgeAddress) && web3) {
            try {
                return new web3.eth.Contract(Bridge, bridgeAddress);
            }
            catch (e) {
                if (e instanceof Error) {
                    console.error(`error initializing contract at [${bridgeAddress}]: ${e.message}`, { stack: e.stack });
                }
            }
        }
        else {
            console.warn(`incorrect bridge address [${bridgeAddress}]`);
        }
    }, [web3, bridgeAddress]);
    const tokenContract = useMemo(() => {
        if (isEthAddress(tokenAddress) && web3) {
            try {
                return new web3.eth.Contract(ERC20, tokenAddress);
            }
            catch (e) {
                if (e instanceof Error) {
                    console.warn(`error initializing contract at [${tokenAddress}]: ${e.message}`, { stack: e.stack });
                }
            }
        }
        else {
            console.warn(`incorrect token address [${tokenAddress}]`);
        }
    }, [web3, tokenAddress]);
    const updateBalance = useCallback(async () => {
        if (address && tokenContract && decimals) {
            const balance = await tokenContract.methods.balanceOf(address).call();
            setBalance(new BigNumber(10).pow(-decimals).times(balance));
        }
    }, [address, tokenContract, decimals]);
    useEffect(() => {
        const timeout = setTimeout(async () => {
            if (tokenContract) {
                try {
                    const decimals = Number(await tokenContract.methods.decimals().call());
                    const symbol = await tokenContract.methods.symbol().call();
                    if (!Number.isFinite(decimals)) {
                        throw new Error("invalid token decimals");
                    }
                    if (!symbol) {
                        throw new Error("invalid token symbol");
                    }
                    setDecimals(decimals);
                    setSymbol(symbol);
                    if (address) {
                        const balance = await tokenContract.methods
                            .balanceOf(address)
                            .call();
                        setBalance(new BigNumber(10).pow(-decimals).times(balance));
                        if (spender) {
                            const allowance = await tokenContract.methods
                                .allowance(address, spender)
                                .call();
                            setAllowance(new BigNumber(10).pow(-decimals).times(allowance));
                        }
                        else {
                            setAllowance(undefined);
                        }
                    }
                    else {
                        setBalance(undefined);
                    }
                    if (spender) {
                        const bridgeBalance = await tokenContract.methods
                            .balanceOf(spender)
                            .call();
                        setBridgeBalance(new BigNumber(10).pow(-decimals).times(bridgeBalance));
                    }
                    else {
                        setBridgeBalance(undefined);
                    }
                    return;
                }
                catch (e) {
                    if (e instanceof Error) {
                        console.error(`token info error: ${e.message}`);
                    }
                }
            }
            setSymbol(undefined);
            setDecimals(undefined);
            setBalance(undefined);
            setAllowance(undefined);
        }, 50);
        return () => {
            clearTimeout(timeout);
        };
    }, [address, spender, tokenContract, bridgeAddress]);
    const approve = useCallback(async (amount) => {
        if (!Number.isFinite(Number(amount))) {
            throw new Error(`incorrect amount: [${amount}]`);
        }
        if (!Number.isFinite(decimals)) {
            throw new Error(`incorrect decimals: [${decimals}]`);
        }
        if (!isEthAddress(address)) {
            throw new Error(`incorrect address: [${address}]`);
        }
        if (!isEthAddress(spender)) {
            throw new Error(`incorrect spender: [${spender}]`);
        }
        if (!tokenContract) {
            throw new Error(`incorrect token contract`);
        }
        if (!balance?.gte(amount)) {
            throw new Error(`low balance`);
        }
        const sendArgs = { from: address };
        if (["0x1", "0x4"].includes(chainId ?? "")) {
            sendArgs.type = "0x2";
        }
        const tx = await tokenContract.methods
            .approve(spender, serializeTokenAmount(amount, decimals))
            .send(sendArgs);
        const allowance = await tokenContract.methods
            .allowance(address, spender)
            .call();
        setAllowance(new BigNumber(10).pow(-decimals).times(allowance));
        return tx;
    }, [address, balance, decimals, tokenContract, spender, chainId]);
    const deposit = useCallback(async (amount, polkadotAddress) => {
        if (!utilCrypto?.decodeAddress || !util?.u8aToHex) {
            throw new Error("base not initialized");
        }
        if (!Number.isFinite(Number(amount))) {
            throw new Error(`incorrect amount: [${amount}]`);
        }
        if (!Number.isFinite(decimals)) {
            throw new Error(`incorrect decimals: [${decimals}]`);
        }
        if (!isEthAddress(address)) {
            throw new Error(`incorrect address: [${address}]`);
        }
        if (!isEthAddress(spender)) {
            throw new Error(`incorrect spender: [${spender}]`);
        }
        if (!validateSubstrateAddress(polkadotAddress)) {
            throw new Error(`incorrect polkadot address: [${polkadotAddress}]`);
        }
        if (!allowance?.gte(amount)) {
            throw new Error(`low allowance: [${allowance?.toString(10)}]`);
        }
        if (!bridgeContract) {
            throw new Error(`incorrect bridge contract`);
        }
        if (!tokenContract) {
            throw new Error(`incorrect token contract`);
        }
        const sendArgs = {
            from: address,
            value: "1000000000000000",
        };
        if (["0x1", "0x4"].includes(chainId ?? "")) {
            sendArgs.type = "0x2";
        }
        const tx = await bridgeContract.methods
            .deposit(dest, resourceId, serializeDepositPayload(amount, decimals, polkadotAddress, {
            hex: util.u8aToHex,
            dec: utilCrypto.decodeAddress,
            validate: validateSubstrateAddress
        }))
            .send(sendArgs);
        const nextBalance = await tokenContract.methods.balanceOf(address).call();
        setBalance(new BigNumber(10).pow(-decimals).times(nextBalance));
        const nextAllowance = await tokenContract.methods
            .allowance(address, spender)
            .call();
        setAllowance(new BigNumber(10).pow(-decimals).times(nextAllowance));
        return tx;
    }, [
        address,
        allowance,
        decimals,
        dest,
        resourceId,
        bridgeContract,
        tokenContract,
        spender,
        chainId,
        util?.u8aToHex,
        utilCrypto?.decodeAddress,
        validateSubstrateAddress
    ]);
    return {
        allowance,
        balance,
        symbol,
        approve,
        deposit,
        updateBalance,
        bridgeBalance,
    };
};
const ethDecimals = 18;
export const useWrappedETH = (wethAddress) => {
    const { address, web3, chainId } = useEthereum();
    const [ethBalance, setEthBalance] = useState();
    const [balance, setBalance] = useState();
    const [symbol, setSymbol] = useState();
    const [decimals, setDecimals] = useState();
    const contract = useMemo(() => {
        if (isEthAddress(wethAddress) && web3) {
            try {
                return new web3.eth.Contract(WETH, wethAddress);
            }
            catch (e) {
                if (e instanceof Error) {
                    console.error(`error initializing contract at [${wethAddress}]: ${e.message}`, { stack: e.stack });
                }
            }
        }
        else {
            console.warn(`incorrect weth address [${wethAddress}]`);
        }
    }, [web3, wethAddress]);
    useEffect(() => {
        const timeout = setTimeout(async () => {
            if (isEthAddress(address) && web3) {
                try {
                    const balance = await web3.eth.getBalance(address);
                    setEthBalance(new BigNumber(10).pow(-ethDecimals).times(balance));
                    return;
                }
                catch (e) {
                    if (e instanceof Error) {
                        console.error(`failed to retreive eth balance: ${e.message}`);
                    }
                }
            }
            setEthBalance(undefined);
        }, 50);
        return () => {
            clearTimeout(timeout);
        };
    }, [address, web3, chainId]);
    useEffect(() => {
        const timeout = setTimeout(async () => {
            if (contract) {
                try {
                    const decimals = Number(await contract.methods.decimals().call());
                    const symbol = await contract.methods.symbol().call();
                    if (!Number.isFinite(decimals)) {
                        throw new Error("invalid token decimals");
                    }
                    if (!symbol) {
                        throw new Error("invalid token symbol");
                    }
                    setDecimals(decimals);
                    setSymbol(symbol);
                    if (address) {
                        const balance = await contract.methods.balanceOf(address).call();
                        setBalance(new BigNumber(10).pow(-decimals).times(balance));
                    }
                    else {
                        setBalance(undefined);
                    }
                    return;
                }
                catch (e) {
                    if (e instanceof Error) {
                        console.warn(`token info error: ${e.message}`);
                    }
                }
            }
            setSymbol(undefined);
            setDecimals(undefined);
            setBalance(undefined);
        }, 50);
        return () => {
            clearTimeout(timeout);
        };
    }, [address, contract, chainId]);
    const deposit = useCallback(async (amount) => {
        if (!Number.isFinite(Number(amount))) {
            throw new Error(`incorrect amount: [${amount}]`);
        }
        if (!Number.isFinite(decimals)) {
            throw new Error(`incorrect decimals: [${decimals}]`);
        }
        if (!isEthAddress(address)) {
            throw new Error(`incorrect address: [${address}]`);
        }
        if (!ethBalance?.gte(amount)) {
            throw new Error(`low balance`);
        }
        if (!contract) {
            throw new Error(`incorrect bridge contract`);
        }
        const sendArgs = {
            from: address,
            value: serializeTokenAmount(amount, ethDecimals),
        };
        if (["0x1", "0x4"].includes(chainId ?? "")) {
            sendArgs.type = "0x2";
        }
        const tx = await contract.methods.deposit().send(sendArgs);
        const nextEthBalance = await web3.eth.getBalance(address);
        setEthBalance(new BigNumber(10).pow(-ethDecimals).times(nextEthBalance));
        const nextBalance = await contract.methods.balanceOf(address).call();
        setBalance(new BigNumber(10).pow(-decimals).times(nextBalance));
        return tx;
    }, [address, ethBalance, decimals, contract, chainId]);
    const withdraw = useCallback(async (amount) => {
        if (!Number.isFinite(Number(amount))) {
            throw new Error(`incorrect amount: [${amount}]`);
        }
        if (!Number.isFinite(decimals)) {
            throw new Error(`incorrect decimals: [${decimals}]`);
        }
        if (!isEthAddress(address)) {
            throw new Error(`incorrect address: [${address}]`);
        }
        if (!balance?.gte(amount)) {
            throw new Error(`low balance`);
        }
        if (!contract) {
            throw new Error(`incorrect bridge contract`);
        }
        const sendArgs = {
            from: address,
        };
        if (["0x1", "0x4"].includes(chainId ?? "")) {
            sendArgs.type = "0x2";
        }
        const tx = await contract.methods
            .withdraw(serializeTokenAmount(amount, decimals))
            .send(sendArgs);
        const nextEthBalance = await web3.eth.getBalance(address);
        setEthBalance(new BigNumber(10).pow(-ethDecimals).times(nextEthBalance));
        const nextBalance = await contract.methods.balanceOf(address).call();
        setBalance(new BigNumber(10).pow(-decimals).times(nextBalance));
        return tx;
    }, [address, balance, decimals, contract, chainId]);
    return { ethBalance, balance, symbol, deposit, withdraw };
};
//# sourceMappingURL=hooks.js.map