import { isAddress } from "viem";
import { utils } from "ethers";
import bs58 from "bs58";
import { TronWeb } from "tronweb";
import { BigNumber, chainIsEvm, chainIsSvm, chainIsTvm } from "./";
/**
 * Verify whether an address' bytecode resembles an EIP-7702 delegation.
 * @param code Bytecode for a given address.
 * @returns True if the bytecode resembles an EIP-7702 delegation, otherwise false.
 */
export function is7702Delegate(code) {
    // Sample 7702 delegation bytecode: 0xef010063c0c19a282a1b52b07dd5a65b58948a07dae32b
    return code.length === 48 && code.startsWith("0xef0100") && isAddress(`0x${code.slice(8)}`);
}
/**
 * Checks if a contract is deployed at the given address
 * @param address The ETH address to check
 * @param provider A valid Ethers.js provider
 * @param ignore7702 A boolean to indicate whether EIP-7702 delegations should be considered as contract code.
 * @returns A boolean indicating if a contract is deployed at the given address or not (true = contract, false = no contract)
 */
export async function isContractDeployedToAddress(address, provider, ignore7702 = false) {
    // A base case for if the address is null or malformed
    if (!address || !isAddress(address)) {
        return false;
    }
    const code = await provider.getCode(address);
    if (code === "0x") {
        return false;
    }
    // Ignore EIP-7702 delegations if ignore7702 was set.
    if (ignore7702) {
        return !is7702Delegate(code);
    }
    return true;
}
export function compareAddresses(addressA, addressB) {
    // Convert address strings to BigNumbers and then sort numerical value of the BigNumber, which sorts the addresses
    // effectively by their hex value.
    const bnAddressA = BigNumber.from(addressA);
    const bnAddressB = BigNumber.from(addressB);
    if (bnAddressA.gt(bnAddressB)) {
        return 1;
    }
    else if (bnAddressA.lt(bnAddressB)) {
        return -1;
    }
    else {
        return 0;
    }
}
export function compareAddressesSimple(addressA, addressB) {
    if (addressA === undefined || addressB === undefined) {
        return false;
    }
    return addressA.toLowerCase() === addressB.toLowerCase();
}
// Converts an input hex data string into a bytes32 string. Note that the output bytes will be lowercase
// so that it naturally matches with ethers event data.
// Throws an error if the input string is already greater than 32 bytes.
export function toBytes32(address) {
    return utils.hexZeroPad(address, 32).toLowerCase();
}
// Constructs a 20-byte checksummed EVM address from a hex string input.
// Throws an error if the underlying address length is longer than 20 bytes or incorrectly checksummed.
export function toEvmAddress(hexString) {
    // rawAddress is the address which is not properly checksummed.
    const rawAddress = utils.hexZeroPad(utils.hexStripZeros(hexString), 20);
    return utils.getAddress(rawAddress);
}
export function isValidEvmAddress(address) {
    if (isAddress(address)) {
        return true;
    }
    // We may throw an error here if hexZeroPadFails. This will happen if the address to pad is greater than 20 bytes long, indicating
    // that the address had less than 12 leading zero bytes.
    // We may also throw at getAddress if the input cannot be converted into a checksummed EVM address for some reason.
    // For both cases, this indicates that the address cannot be casted as a bytes20 EVM address, so we should return false.
    try {
        const evmAddress = utils.hexZeroPad(utils.hexStripZeros(address), 20);
        return isAddress(utils.getAddress(evmAddress));
    }
    catch {
        return false;
    }
}
/**
 * Creates the proper address type given the input chain ID corresponding to the address's origin network.
 * @param address Stringified address type to convert. Can be either hex encoded or base58 encoded.
 * @param chainId Chain ID for the intended use of the address.
 * @returns a child `Address` type most fitting for the chain ID.
 * @todo: Change this to `toAddress` once we remove the other `toAddress` function.
 */
export function toAddressType(address, chainId) {
    const rawAddress = address.startsWith("0x") ? utils.arrayify(address) : bs58.decode(address);
    if (chainIsTvm(chainId))
        return TvmAddress.from(address);
    else if (chainIsEvm(chainId) && EvmAddress.validate(rawAddress))
        return new EvmAddress(rawAddress);
    else if (chainIsSvm(chainId) && SvmAddress.validate(rawAddress))
        return new SvmAddress(rawAddress);
    return new RawAddress(rawAddress);
}
// The Address class can contain any address type. It is up to the subclasses to determine how to format the address's internal representation,
// which for this class, is a bytes32 hex string.
export class Address {
    __address_type_brand = true;
    rawAddress;
    // Keep all address types in cache so that we may lazily evaluate them when necessary.
    evmAddress = undefined;
    bytes32Address = undefined;
    svmAddress = undefined;
    constructor(_rawAddress) {
        // The only validation done here is checking that the address is at most a 32-byte address, which  will be well-defined on any supported network.
        // Further validation is done on the subclasses so that we can guarantee that, for example, an EvmAddress type will always contain a valid, 20-byte
        // EVM address.
        if (_rawAddress.length > 32) {
            throw new Error(`Address ${utils.hexlify(_rawAddress)} cannot be longer than 32 bytes.`);
        }
        // Ensure all addresses in this class are internally stored as 32 bytes.
        this.rawAddress = utils.zeroPad(_rawAddress, 32);
    }
    static isAddress(obj) {
        return typeof obj === "object" && obj !== null && "__address_type_brand" in obj;
    }
    toJSON() {
        return this.toNative();
    }
    // Converts the address into a bytes32 string. Note that the output bytes will be lowercase so that it matches ethers event data. This function will never
    // throw since address length validation was done at construction time.
    toBytes32() {
        return (this.bytes32Address ??= utils.hexZeroPad(utils.hexlify(this.rawAddress), 32).toLowerCase());
    }
    // Returns last 20 bytes of Address as a hex string. Truncates if necessary. This function is useful for comparing some longer addresses(e.g. Solana) to Solidity events
    // that contain truncated `address` type as one of the fields
    truncateToBytes20() {
        // Take the last 20 bytes
        const bytes20 = this.rawAddress.slice(-20);
        return toEvmAddress(utils.hexlify(bytes20));
    }
    // Converts the address (can be bytes32 or bytes20) to its base58 counterpart. This conversion will always succeed, even if the input address is not valid on Solana,
    // as this address may be needed to represent an EVM address on Solana.
    toBase58() {
        return (this.svmAddress ??= bs58.encode(this.rawAddress));
    }
    // Converts the address to a valid EVM address. If it is unable to convert the address to a valid EVM address for some reason, such as if this address
    // is longer than 20 bytes, then this function will throw an error.
    toEvmAddress() {
        const parseRawAddress = () => {
            const hexString = utils.hexlify(this.rawAddress);
            const rawAddress = utils.hexZeroPad(utils.hexStripZeros(hexString), 20);
            return utils.getAddress(rawAddress);
        };
        return (this.evmAddress ??= parseRawAddress());
    }
    // Converts the address to a hex string. This method should be overridden by subclasses to obtain more meaningful
    // address representations for the target chain ID.
    toNative() {
        return this.toBytes32();
    }
    // Implements `Hexable` for `Address`. Needed for encoding purposes. This class is treated by default as a bytes32 primitive type, but can change for subclasses.
    toHexString() {
        return this.toBytes32();
    }
    // Checks if the address is valid on the given chain ID.
    isValidOn(chainId) {
        if (chainIsTvm(chainId))
            return TvmAddress.validate(this.rawAddress);
        if (chainIsEvm(chainId))
            return EvmAddress.validate(this.rawAddress);
        if (chainIsSvm(chainId))
            return SvmAddress.validate(this.rawAddress);
        return false;
    }
    // Converts the input address to a 32-byte hex data string.
    toString() {
        return this.toNative();
    }
    // Checks if the address is the zero address.
    isZeroAddress() {
        return utils.stripZeros(this.rawAddress).length === 0;
    }
    // Forces `rawAddress` to become an SvmAddress type. This will only throw if `rawAddress.length > 32`.
    forceSvmAddress() {
        return SvmAddress.from(this.toBase58());
    }
    // Checks if the other address is equivalent to this address.
    eq(other) {
        return this.toString() === other?.toString();
    }
    // Compares Addresses by their raw byte representation (big-endian).
    // note: Intended for use when sorting like addresses.
    compare(otherAddress) {
        for (let i = 0; i < 32; i++) {
            if (this.rawAddress[i] > otherAddress.rawAddress[i])
                return 1;
            if (this.rawAddress[i] < otherAddress.rawAddress[i])
                return -1;
        }
        return 0;
    }
    isEVM() {
        return false;
    }
    isSVM() {
        return false;
    }
    isTVM() {
        return false;
    }
}
// Subclass of address which strictly deals with 20-byte addresses. These addresses are guaranteed to be valid EVM addresses, so `toAddress` will always succeed.
export class EvmAddress extends Address {
    // On construction, validate that the address can indeed be coerced into an EVM address. Throw immediately if it cannot.
    constructor(rawAddress) {
        if (!EvmAddress.validate(rawAddress)) {
            throw new Error(`${utils.hexlify(rawAddress)} is not a valid EVM address`);
        }
        super(rawAddress);
    }
    static validate(rawAddress) {
        return (rawAddress.length == 20 || (rawAddress.length === 32 && rawAddress.slice(0, 12).every((field) => field === 0)));
    }
    isEVM() {
        return true;
    }
    // Override `toAddress` to return the 20-byte representation address.
    toNative() {
        return this.toEvmAddress();
    }
    // Constructs a new EvmAddress type.
    static from(address) {
        return address.startsWith("0x") ? new this(utils.arrayify(address)) : new this(bs58.decode(address));
    }
}
// Subclass of address which strictly deals SVM addresses. These addresses are guaranteed to be valid SVM addresses, so `toBase58` will always produce a valid Solana address.
export class SvmAddress extends Address {
    // On construction, validate that the address is a point on Curve25519. Throw immediately if it is not.
    constructor(rawAddress) {
        if (!SvmAddress.validate(rawAddress)) {
            throw new Error(`${utils.hexlify(rawAddress)} is not a valid SVM address`); // @todo: Display as Base58?
        }
        super(rawAddress);
    }
    static validate(rawAddress) {
        // Deliberately invalidate SVM addresses w/ the upper 12 bytes zeroed. These addresses are technically valid
        // but highly improbable and are much more likely to be a mistaken interpretation of an EVM address. Err on
        // the side of caution for the time being. Exception: Permit the zero address (i.e. for exclusiverRelayer).
        return (rawAddress.length === 32 &&
            (!rawAddress.slice(0, 12).every((field) => field === 0) || rawAddress.every((field) => field === 0)));
    }
    isSVM() {
        return true;
    }
    // Override the toAddress function for SVM addresses only since while they will never have a defined 20-byte representation. The base58 encoded addresses are also the encodings
    // used in TOKEN_SYMBOLS_MAP.
    toNative() {
        return this.toBase58();
    }
    // Constructs a new SvmAddress type.
    static from(address) {
        return address.startsWith("0x") ? new this(utils.arrayify(address)) : new this(bs58.decode(address));
    }
}
// Subclass of address which handles TRON (TVM) addresses. Internally stores a 20-byte address (same as EVM),
// but toNative() returns the TRON Base58Check-encoded address (T...).
export class TvmAddress extends Address {
    nativeAddress = undefined;
    constructor(rawAddress) {
        if (!TvmAddress.validate(rawAddress)) {
            throw new Error(`${utils.hexlify(rawAddress)} is not a valid TVM address`);
        }
        super(rawAddress);
    }
    static validate(rawAddress) {
        return (rawAddress.length == 20 || (rawAddress.length === 32 && rawAddress.slice(0, 12).every((field) => field === 0)));
    }
    isTVM() {
        return true;
    }
    // Returns the TRON Base58Check-encoded address (T...).
    toNative() {
        const computeNative = () => {
            const hexString = utils.hexlify(this.rawAddress);
            // Strip leading zeros to get the 20-byte portion, then convert to TRON format.
            const evmHex = utils.hexZeroPad(utils.hexStripZeros(hexString), 20);
            return TronWeb.address.fromHex(evmHex);
        };
        return (this.nativeAddress ??= computeNative());
    }
    toHexString() {
        return this.toEvmAddress();
    }
    // Constructs a new TvmAddress from a string. Accepts both 0x-prefixed hex and TRON Base58Check addresses.
    static from(address) {
        if (address.startsWith("0x")) {
            return new this(utils.arrayify(address));
        }
        // For base58 Tron addresses, convert via TronWeb to get the hex representation.
        const hex = TronWeb.address.toHex(address);
        // TronWeb returns hex with a 0x41 prefix (TRON's address byte). Strip 0x41 to get 20 bytes.
        const rawHex = "0x" + hex.slice(2);
        return new this(utils.arrayify(rawHex));
    }
}
export class RawAddress extends Address {
}
//# sourceMappingURL=AddressUtils.js.map