import { __awaiter, __generator } from "tslib";
import assert from "assert";
import { Contract } from "ethers";
import * as constants from "../constants";
import { ERC20__factory } from "../typechain";
import { getNetworkName, chainIsL1, chainIsProd } from "./NetworkUtils";
import { isDefined } from "./TypeGuards";
import { EvmAddress, toAddressType } from "./AddressUtils";
var TOKEN_SYMBOLS_MAP = constants.TOKEN_SYMBOLS_MAP, CHAIN_IDs = constants.CHAIN_IDs, TOKEN_EQUIVALENCE_REMAPPING = constants.TOKEN_EQUIVALENCE_REMAPPING;
export function fetchTokenInfo(address, signerOrProvider) {
    return __awaiter(this, void 0, void 0, function () {
        var token, _a, symbol, decimals;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    token = new Contract(address, ERC20__factory.abi, signerOrProvider);
                    return [4 /*yield*/, Promise.all([token.symbol(), token.decimals()])];
                case 1:
                    _a = _b.sent(), symbol = _a[0], decimals = _a[1];
                    return [2 /*return*/, { address: EvmAddress.from(address), symbol: symbol, decimals: decimals }];
            }
        });
    });
}
export var getL2TokenAddresses = function (l1TokenAddress, l1ChainId) {
    var _a;
    if (l1ChainId === void 0) { l1ChainId = CHAIN_IDs.MAINNET; }
    return (_a = Object.values(TOKEN_SYMBOLS_MAP).find(function (details) {
        return details.addresses[l1ChainId] === l1TokenAddress;
    })) === null || _a === void 0 ? void 0 : _a.addresses;
};
/**
 * Resolve a token symbol to an L1Token description on a particular chain ID.
 * @notice Not to be confused with the HubPool's internal view on the supported origin/destination token for a chain.
 * @param symbol Symbol to query.
 * @param chainId Chain ID to query on.
 * @returns Symbol, decimals and contract address on the requested chain.
 */
export function resolveSymbolOnChain(chainId, symbol) {
    // @dev Suppress tsc complaints by casting symbol to the expected type.
    var token = TOKEN_SYMBOLS_MAP[symbol];
    if (!isDefined(token) || !isDefined(token.addresses[chainId])) {
        var network = getNetworkName(chainId);
        throw new Error("Unable to find token ".concat(symbol, " on ").concat(network, " (chain ID ").concat(chainId));
    }
    var decimals = token.decimals, addresses = token.addresses;
    var address = toAddressType(addresses[chainId], chainId);
    assert(address.isEVM() || address.isSVM());
    return { symbol: symbol, decimals: decimals, address: address };
}
/**
 * Returns the contract address for a given token symbol and chainId.
 * @param symbol A case-insensitive token symbol.
 * @param chainId The chainId to resolve the contract address for.
 * @param tokenMapping A parameter to determine where to source token information. Defaults to the constants variant.
 * @returns The contract address for the given token symbol and chainId, or undefined if the token symbol is not supported.
 */
export var resolveContractFromSymbol = function (symbol, chainId, tokenMapping) {
    var _a;
    if (tokenMapping === void 0) { tokenMapping = TOKEN_SYMBOLS_MAP; }
    return (_a = Object.values(tokenMapping).find(function (details) {
        return details.symbol.toLowerCase() === symbol.toLowerCase();
    })) === null || _a === void 0 ? void 0 : _a.addresses[Number(chainId)];
};
export function getCoingeckoTokenIdByAddress(address, chainId) {
    var token = getTokenInfo(toAddressType(address, chainId), chainId);
    return TOKEN_SYMBOLS_MAP[token.symbol].coingeckoId;
}
/**
 * Retrieves the ERC20 balance for a given address and token address.
 * @param address The address to retrieve the balance for.
 * @param tokenAddress The token address
 * @param signerOrProvider A valid ethers.js Signer or Provider object.
 * @param blockTag The block tag to retrieve the balance at.
 * @returns The balance of the given address for the given token address.
 */
export function getTokenBalance(address, tokenAddress, signerOrProvider, blockTag) {
    if (blockTag === void 0) { blockTag = "latest"; }
    var token = ERC20__factory.connect(tokenAddress, signerOrProvider);
    return token.balanceOf(address, { blockTag: blockTag });
}
export function isBridgedUsdc(tokenSymbol) {
    return !!constants.BRIDGED_USDC_SYMBOLS.find(function (bridgedUsdcSymbol) { return bridgedUsdcSymbol.toLowerCase() === tokenSymbol.toLowerCase(); });
}
export function isStablecoin(tokenSymbol) {
    return constants.STABLE_COIN_SYMBOLS.some(function (stablecoinSymbol) { return stablecoinSymbol.toLowerCase() === tokenSymbol.toLowerCase(); });
}
/**
 * @notice Returns the Token info for the token mapping in TOKEN_SYMBOLS_MAP matching the given l2TokenAddress
 * and chainId. If the chain is the hub chain, then will remap the L1 token to its equivalent L1 token symbol for example
 * it will always return a token info with symbol USDC and never USDC.e if chainId = mainnet.
 * @param l2Token Address instance.
 * @param chainId ChainId for l2Token address.
 * @param tokenMapping Optional custom token mapping.
 * @returns TokenInfo object.
 */
export function getTokenInfo(l2Token, chainId, tokenMapping) {
    var _a;
    if (tokenMapping === void 0) { tokenMapping = TOKEN_SYMBOLS_MAP; }
    var address = l2Token.toNative();
    // @dev This might give false positives if tokens on different networks have the same address. I'm not sure how
    // to get around this...
    var tokenObject = Object.values(tokenMapping).find(function (_a) {
        var addresses = _a.addresses;
        return addresses[chainId] === address;
    });
    if (!tokenObject) {
        throw new Error("Unable to resolve token info for ".concat(address, " on chain ").concat(chainId));
    }
    if (chainIsL1(chainId)) {
        var l1TokenSymbol = (_a = TOKEN_EQUIVALENCE_REMAPPING[tokenObject.symbol]) !== null && _a !== void 0 ? _a : tokenObject.symbol;
        tokenObject = tokenMapping[l1TokenSymbol];
    }
    return { address: l2Token, symbol: tokenObject.symbol, decimals: tokenObject.decimals };
}
/**
 * Get the USDC symbol for the given token address and chain ID.
 * @param l2Token A Web3 token address (not case sensitive)
 * @param chainId A chain Id to reference
 * @returns Either USDC (if native) or USDbC/USDC.e (if bridged) or undefined if the token address is not recognized.
 */
export function getUsdcSymbol(l2Token, chainId) {
    var IterableTokenSymbolsMap = TOKEN_SYMBOLS_MAP;
    return ["USDC", "USDbC", "USDC.e"].find(function (token) { var _a, _b; return ((_b = (_a = IterableTokenSymbolsMap[token]) === null || _a === void 0 ? void 0 : _a.addresses) === null || _b === void 0 ? void 0 : _b[chainId]) === l2Token.toNative(); });
}
/**
 * @notice Returns the l1 token address matching the given l2TokenAddress and chainId.
 */
export function getL1TokenAddress(l2TokenAddress, chainId) {
    if (chainIsL1(chainId)) {
        assert(l2TokenAddress.isEVM());
        return l2TokenAddress;
    }
    var tokenObject = Object.values(TOKEN_SYMBOLS_MAP).find(function (_a) {
        var addresses = _a.addresses;
        return l2TokenAddress.toNative() === addresses[chainId];
    });
    var l1TokenAddress = tokenObject === null || tokenObject === void 0 ? void 0 : tokenObject.addresses[chainIsProd(chainId) ? CHAIN_IDs.MAINNET : CHAIN_IDs.SEPOLIA];
    if (!l1TokenAddress) {
        throw new Error("getL1TokenAddress: Unable to resolve l1 token for L2 token ".concat(l2TokenAddress, " on chain ").concat(chainId));
    }
    return EvmAddress.from(l1TokenAddress);
}
//# sourceMappingURL=TokenUtils.js.map