ETH Price: $2,487.68 (+1.24%)

Transaction Decoder

Block:
19615431 at Apr-09-2024 03:32:47 AM +UTC
Transaction Fee:
0.001007463211955346 ETH $2.51
Gas Used:
46,038 Gas / 21.883296667 Gwei

Emitted Events:

345 Zeta.Transfer( from=[Sender] 0xb23304d73ea53aa80b77f6a29daad2ccfc22c582, to=[Receiver] ZetaConnectorEth, value=2615508890000000000 )
346 Zeta.Approval( owner=[Sender] 0xb23304d73ea53aa80b77f6a29daad2ccfc22c582, spender=[Receiver] ZetaConnectorEth, value=0 )
347 ZetaConnectorEth.ZetaSent( sourceTxOriginAddress=[Sender] 0xb23304d73ea53aa80b77f6a29daad2ccfc22c582, zetaTxSenderAddress=[Sender] 0xb23304d73ea53aa80b77f6a29daad2ccfc22c582, destinationChainId=7000, destinationAddress=[Sender] 0xb23304d73ea53aa80b77f6a29daad2ccfc22c582, zetaValueAndGas=2615508890000000000, destinationGasLimit=100000, message=0x, zetaParams=0x )

Account State Difference:

  Address   Before After State Difference Code
(Titan Builder)
10.218454532468146037 Eth10.218454578506146037 Eth0.000000046038
0xb23304d7...Cfc22c582
0.001771247591579122 Eth
Nonce: 5
0.000763784379623776 Eth
Nonce: 6
0.001007463211955346
0xf091867E...9D82e9cc8

Execution Trace

ZetaConnectorEth.send( input=[{name:destinationChainId, type:uint256, order:1, indexed:false, value:7000, valueString:7000}, {name:destinationAddress, type:bytes, order:2, indexed:false, value:0xB23304D73EA53AA80B77F6A29DAAD2CCFC22C582, valueString:0xB23304D73EA53AA80B77F6A29DAAD2CCFC22C582}, {name:destinationGasLimit, type:uint256, order:3, indexed:false, value:100000, valueString:100000}, {name:message, type:bytes, order:4, indexed:false, value:0x, valueString:0x}, {name:zetaValueAndGas, type:uint256, order:5, indexed:false, value:2615508890000000000, valueString:2615508890000000000}, {name:zetaParams, type:bytes, order:6, indexed:false, value:0x, valueString:0x}] )
  • Zeta.transferFrom( sender=0xb23304d73Ea53aa80b77F6a29dAAD2CCfc22c582, recipient=0x000007Cf399229b2f5A4D043F20E90C9C98B7C6a, amount=2615508890000000000 ) => ( True )
    File 1 of 2: ZetaConnectorEth
    // Sources flattened with hardhat v2.13.1 https://hardhat.org
    
    // File @openzeppelin/contracts/utils/[email protected]
    
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
    
    pragma solidity 0.8.7;
    
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
    
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    }
    
    // File @openzeppelin/contracts/security/[email protected]
    
    /**
     * @dev Contract module which allows children to implement an emergency stop
     * mechanism that can be triggered by an authorized account.
     *
     * This module is used through inheritance. It will make available the
     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
     * the functions of your contract. Note that they will not be pausable by
     * simply including this module, only once the modifiers are put in place.
     */
    abstract contract Pausable is Context {
        /**
         * @dev Emitted when the pause is triggered by `account`.
         */
        event Paused(address account);
    
        /**
         * @dev Emitted when the pause is lifted by `account`.
         */
        event Unpaused(address account);
    
        bool private _paused;
    
        /**
         * @dev Initializes the contract in unpaused state.
         */
        constructor() {
            _paused = false;
        }
    
        /**
         * @dev Modifier to make a function callable only when the contract is not paused.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        modifier whenNotPaused() {
            _requireNotPaused();
            _;
        }
    
        /**
         * @dev Modifier to make a function callable only when the contract is paused.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        modifier whenPaused() {
            _requirePaused();
            _;
        }
    
        /**
         * @dev Returns true if the contract is paused, and false otherwise.
         */
        function paused() public view virtual returns (bool) {
            return _paused;
        }
    
        /**
         * @dev Throws if the contract is paused.
         */
        function _requireNotPaused() internal view virtual {
            require(!paused(), "Pausable: paused");
        }
    
        /**
         * @dev Throws if the contract is not paused.
         */
        function _requirePaused() internal view virtual {
            require(paused(), "Pausable: not paused");
        }
    
        /**
         * @dev Triggers stopped state.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        function _pause() internal virtual whenNotPaused {
            _paused = true;
            emit Paused(_msgSender());
        }
    
        /**
         * @dev Returns to normal state.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        function _unpause() internal virtual whenPaused {
            _paused = false;
            emit Unpaused(_msgSender());
        }
    }
    
    // File @openzeppelin/contracts/token/ERC20/[email protected]
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
    
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
    
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
    
        /**
         * @dev Moves `amount` tokens from the caller's account to `to`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address to, uint256 amount) external returns (bool);
    
        /**
         * @dev Returns the remaining number of tokens that `spender` will be
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
         * zero by default.
         *
         * This value changes when {approve} or {transferFrom} are called.
         */
        function allowance(address owner, address spender) external view returns (uint256);
    
        /**
         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
         * that someone may use both the old and the new allowance by unfortunate
         * transaction ordering. One possible solution to mitigate this race
         * condition is to first reduce the spender's allowance to 0 and set the
         * desired value afterwards:
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
         *
         * Emits an {Approval} event.
         */
        function approve(address spender, uint256 amount) external returns (bool);
    
        /**
         * @dev Moves `amount` tokens from `from` to `to` using the
         * allowance mechanism. `amount` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(address from, address to, uint256 amount) external returns (bool);
    }
    
    // File contracts/evm/interfaces/ConnectorErrors.sol
    
    /**
     * @dev Interface with connector custom errors
     */
    interface ConnectorErrors {
        // @dev Thrown when caller is not the address defined as paused address
        error CallerIsNotPauser(address caller);
    
        // @dev Thrown when caller is not the address defined as TSS address
        error CallerIsNotTss(address caller);
    
        // @dev Thrown when caller is not the address defined as TSS Updater address
        error CallerIsNotTssUpdater(address caller);
    
        // @dev Thrown when caller is not the address defined as TSS or TSS Updater address
        error CallerIsNotTssOrUpdater(address caller);
    
        // @dev Thrown when Zeta can't be transferred for some reason
        error ZetaTransferError();
    
        // @dev Thrown when maxSupply will be exceed if minting will proceed
        error ExceedsMaxSupply(uint256 maxSupply);
    }
    
    // File contracts/evm/interfaces/ZetaInterfaces.sol
    
    interface ZetaInterfaces {
        /**
         * @dev Use SendInput to interact with the Connector: connector.send(SendInput)
         */
        struct SendInput {
            /// @dev Chain id of the destination chain. More about chain ids https://docs.zetachain.com/learn/glossary#chain-id
            uint256 destinationChainId;
            /// @dev Address receiving the message on the destination chain (expressed in bytes since it can be non-EVM)
            bytes destinationAddress;
            /// @dev Gas limit for the destination chain's transaction
            uint256 destinationGasLimit;
            /// @dev An encoded, arbitrary message to be parsed by the destination contract
            bytes message;
            /// @dev ZETA to be sent cross-chain + ZetaChain gas fees + destination chain gas fees (expressed in ZETA)
            uint256 zetaValueAndGas;
            /// @dev Optional parameters for the ZetaChain protocol
            bytes zetaParams;
        }
    
        /**
         * @dev Our Connector calls onZetaMessage with this struct as argument
         */
        struct ZetaMessage {
            bytes zetaTxSenderAddress;
            uint256 sourceChainId;
            address destinationAddress;
            /// @dev Remaining ZETA from zetaValueAndGas after subtracting ZetaChain gas fees and destination gas fees
            uint256 zetaValue;
            bytes message;
        }
    
        /**
         * @dev Our Connector calls onZetaRevert with this struct as argument
         */
        struct ZetaRevert {
            address zetaTxSenderAddress;
            uint256 sourceChainId;
            bytes destinationAddress;
            uint256 destinationChainId;
            /// @dev Equals to: zetaValueAndGas - ZetaChain gas fees - destination chain gas fees - source chain revert tx gas fees
            uint256 remainingZetaValue;
            bytes message;
        }
    }
    
    interface ZetaConnector {
        /**
         * @dev Sending value and data cross-chain is as easy as calling connector.send(SendInput)
         */
        function send(ZetaInterfaces.SendInput calldata input) external;
    }
    
    interface ZetaReceiver {
        /**
         * @dev onZetaMessage is called when a cross-chain message reaches a contract
         */
        function onZetaMessage(ZetaInterfaces.ZetaMessage calldata zetaMessage) external;
    
        /**
         * @dev onZetaRevert is called when a cross-chain message reverts.
         * It's useful to rollback to the original state
         */
        function onZetaRevert(ZetaInterfaces.ZetaRevert calldata zetaRevert) external;
    }
    
    /**
     * @dev ZetaTokenConsumer makes it easier to handle the following situations:
     *   - Getting Zeta using native coin (to pay for destination gas while using `connector.send`)
     *   - Getting Zeta using a token (to pay for destination gas while using `connector.send`)
     *   - Getting native coin using Zeta (to return unused destination gas when `onZetaRevert` is executed)
     *   - Getting a token using Zeta (to return unused destination gas when `onZetaRevert` is executed)
     * @dev The interface can be implemented using different strategies, like UniswapV2, UniswapV3, etc
     */
    interface ZetaTokenConsumer {
        event EthExchangedForZeta(uint256 amountIn, uint256 amountOut);
        event TokenExchangedForZeta(address token, uint256 amountIn, uint256 amountOut);
        event ZetaExchangedForEth(uint256 amountIn, uint256 amountOut);
        event ZetaExchangedForToken(address token, uint256 amountIn, uint256 amountOut);
    
        function getZetaFromEth(address destinationAddress, uint256 minAmountOut) external payable returns (uint256);
    
        function getZetaFromToken(
            address destinationAddress,
            uint256 minAmountOut,
            address inputToken,
            uint256 inputTokenAmount
        ) external returns (uint256);
    
        function getEthFromZeta(
            address destinationAddress,
            uint256 minAmountOut,
            uint256 zetaTokenAmount
        ) external returns (uint256);
    
        function getTokenFromZeta(
            address destinationAddress,
            uint256 minAmountOut,
            address outputToken,
            uint256 zetaTokenAmount
        ) external returns (uint256);
    
        function hasZetaLiquidity() external view returns (bool);
    }
    
    interface ZetaCommonErrors {
        error InvalidAddress();
    }
    
    // File contracts/evm/ZetaConnector.base.sol
    
    /**
     * @dev Main abstraction of ZetaConnector.
     * This contract manages interactions between TSS and different chains.
     * There's an instance of this contract on each chain supported by ZetaChain.
     */
    contract ZetaConnectorBase is ConnectorErrors, Pausable {
        address public immutable zetaToken;
    
        /**
         * @dev Multisig contract to pause incoming transactions.
         * The responsibility of pausing outgoing transactions is left to the protocol for more flexibility.
         */
        address public pauserAddress;
    
        /**
         * @dev Collectively held by ZetaChain validators.
         */
        address public tssAddress;
    
        /**
         * @dev This address will start pointing to a multisig contract, then it will become the TSS address itself.
         */
        address public tssAddressUpdater;
    
        event ZetaSent(
            address sourceTxOriginAddress,
            address indexed zetaTxSenderAddress,
            uint256 indexed destinationChainId,
            bytes destinationAddress,
            uint256 zetaValueAndGas,
            uint256 destinationGasLimit,
            bytes message,
            bytes zetaParams
        );
    
        event ZetaReceived(
            bytes zetaTxSenderAddress,
            uint256 indexed sourceChainId,
            address indexed destinationAddress,
            uint256 zetaValue,
            bytes message,
            bytes32 indexed internalSendHash
        );
    
        event ZetaReverted(
            address zetaTxSenderAddress,
            uint256 sourceChainId,
            uint256 indexed destinationChainId,
            bytes destinationAddress,
            uint256 remainingZetaValue,
            bytes message,
            bytes32 indexed internalSendHash
        );
    
        event TSSAddressUpdated(address callerAddress, address newTssAddress);
    
        event TSSAddressUpdaterUpdated(address callerAddress, address newTssUpdaterAddress);
    
        event PauserAddressUpdated(address callerAddress, address newTssAddress);
    
        /**
         * @dev Constructor requires initial addresses.
         * zetaToken address is the only immutable one, while others can be updated.
         */
        constructor(address zetaToken_, address tssAddress_, address tssAddressUpdater_, address pauserAddress_) {
            if (
                zetaToken_ == address(0) ||
                tssAddress_ == address(0) ||
                tssAddressUpdater_ == address(0) ||
                pauserAddress_ == address(0)
            ) {
                revert ZetaCommonErrors.InvalidAddress();
            }
    
            zetaToken = zetaToken_;
            tssAddress = tssAddress_;
            tssAddressUpdater = tssAddressUpdater_;
            pauserAddress = pauserAddress_;
        }
    
        /**
         * @dev Modifier to restrict actions to pauser address.
         */
        modifier onlyPauser() {
            if (msg.sender != pauserAddress) revert CallerIsNotPauser(msg.sender);
            _;
        }
    
        /**
         * @dev Modifier to restrict actions to TSS address.
         */
        modifier onlyTssAddress() {
            if (msg.sender != tssAddress) revert CallerIsNotTss(msg.sender);
            _;
        }
    
        /**
         * @dev Modifier to restrict actions to TSS updater address.
         */
        modifier onlyTssUpdater() {
            if (msg.sender != tssAddressUpdater) revert CallerIsNotTssUpdater(msg.sender);
            _;
        }
    
        /**
         * @dev Update the pauser address. The only address allowed to do that is the current pauser.
         */
        function updatePauserAddress(address pauserAddress_) external onlyPauser {
            if (pauserAddress_ == address(0)) revert ZetaCommonErrors.InvalidAddress();
    
            pauserAddress = pauserAddress_;
    
            emit PauserAddressUpdated(msg.sender, pauserAddress_);
        }
    
        /**
         * @dev Update the TSS address. The address can be updated by the TSS updater or the TSS address itself.
         */
        function updateTssAddress(address tssAddress_) external {
            if (msg.sender != tssAddress && msg.sender != tssAddressUpdater) revert CallerIsNotTssOrUpdater(msg.sender);
            if (tssAddress_ == address(0)) revert ZetaCommonErrors.InvalidAddress();
    
            tssAddress = tssAddress_;
    
            emit TSSAddressUpdated(msg.sender, tssAddress_);
        }
    
        /**
         * @dev Changes the ownership of tssAddressUpdater to be the one held by the ZetaChain TSS Signer nodes.
         */
        function renounceTssAddressUpdater() external onlyTssUpdater {
            if (tssAddress == address(0)) revert ZetaCommonErrors.InvalidAddress();
    
            tssAddressUpdater = tssAddress;
            emit TSSAddressUpdaterUpdated(msg.sender, tssAddressUpdater);
        }
    
        /**
         * @dev Pause the input (send) transactions.
         */
    
        function pause() external onlyPauser {
            _pause();
        }
    
        /**
         * @dev Unpause the contract to allow transactions again.
         */
    
        function unpause() external onlyPauser {
            _unpause();
        }
    
        /**
         * @dev Entrypoint to send data and value through ZetaChain.
         */
        function send(ZetaInterfaces.SendInput calldata input) external virtual {}
    
        /**
         * @dev Handler to receive data from other chain.
         * This method can be called only by TSS. Access validation is in implementation.
         */
        function onReceive(
            bytes calldata zetaTxSenderAddress,
            uint256 sourceChainId,
            address destinationAddress,
            uint256 zetaValue,
            bytes calldata message,
            bytes32 internalSendHash
        ) external virtual {}
    
        /**
         * @dev Handler to receive errors from other chain.
         * This method can be called only by TSS. Access validation is in implementation.
         */
        function onRevert(
            address zetaTxSenderAddress,
            uint256 sourceChainId,
            bytes calldata destinationAddress,
            uint256 destinationChainId,
            uint256 remainingZetaValue,
            bytes calldata message,
            bytes32 internalSendHash
        ) external virtual {}
    }
    
    // File contracts/evm/ZetaConnector.eth.sol
    
    /**
     * @dev ETH implementation of ZetaConnector.
     * This contract manages interactions between TSS and different chains.
     * This version is only for Ethereum network because in the other chains we mint and burn and in this one we lock and unlock.
     */
    contract ZetaConnectorEth is ZetaConnectorBase {
        constructor(
            address zetaToken_,
            address tssAddress_,
            address tssAddressUpdater_,
            address pauserAddress_
        ) ZetaConnectorBase(zetaToken_, tssAddress_, tssAddressUpdater_, pauserAddress_) {}
    
        function getLockedAmount() external view returns (uint256) {
            return IERC20(zetaToken).balanceOf(address(this));
        }
    
        /**
         * @dev Entrypoint to send data through ZetaChain
         * This call locks the token on the contract and emits an event with all the data needed by the protocol.
         */
        function send(ZetaInterfaces.SendInput calldata input) external override whenNotPaused {
            bool success = IERC20(zetaToken).transferFrom(msg.sender, address(this), input.zetaValueAndGas);
            if (!success) revert ZetaTransferError();
    
            emit ZetaSent(
                tx.origin,
                msg.sender,
                input.destinationChainId,
                input.destinationAddress,
                input.zetaValueAndGas,
                input.destinationGasLimit,
                input.message,
                input.zetaParams
            );
        }
    
        /**
         * @dev Handler to receive data from other chain.
         * This method can be called only by TSS.
         * Transfers the Zeta tokens to destination and calls onZetaMessage if it's needed.
         */
        function onReceive(
            bytes calldata zetaTxSenderAddress,
            uint256 sourceChainId,
            address destinationAddress,
            uint256 zetaValue,
            bytes calldata message,
            bytes32 internalSendHash
        ) external override onlyTssAddress {
            bool success = IERC20(zetaToken).transfer(destinationAddress, zetaValue);
            if (!success) revert ZetaTransferError();
    
            if (message.length > 0) {
                ZetaReceiver(destinationAddress).onZetaMessage(
                    ZetaInterfaces.ZetaMessage(zetaTxSenderAddress, sourceChainId, destinationAddress, zetaValue, message)
                );
            }
    
            emit ZetaReceived(zetaTxSenderAddress, sourceChainId, destinationAddress, zetaValue, message, internalSendHash);
        }
    
        /**
         * @dev Handler to receive errors from other chain.
         * This method can be called only by TSS.
         * Transfers the Zeta tokens to destination and calls onZetaRevert if it's needed.
         */
        function onRevert(
            address zetaTxSenderAddress,
            uint256 sourceChainId,
            bytes calldata destinationAddress,
            uint256 destinationChainId,
            uint256 remainingZetaValue,
            bytes calldata message,
            bytes32 internalSendHash
        ) external override whenNotPaused onlyTssAddress {
            bool success = IERC20(zetaToken).transfer(zetaTxSenderAddress, remainingZetaValue);
            if (!success) revert ZetaTransferError();
    
            if (message.length > 0) {
                ZetaReceiver(zetaTxSenderAddress).onZetaRevert(
                    ZetaInterfaces.ZetaRevert(
                        zetaTxSenderAddress,
                        sourceChainId,
                        destinationAddress,
                        destinationChainId,
                        remainingZetaValue,
                        message
                    )
                );
            }
    
            emit ZetaReverted(
                zetaTxSenderAddress,
                sourceChainId,
                destinationChainId,
                destinationAddress,
                remainingZetaValue,
                message,
                internalSendHash
            );
        }
    }

    File 2 of 2: Zeta
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
    
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    }
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        function totalSupply() external view returns (uint256);
        function balanceOf(address account) external view returns (uint256);
        function transfer(address recipient, uint256 amount) external returns (bool);
        function allowance(address owner, address spender) external view returns (uint256);
        function approve(address spender, uint256 amount) external returns (bool);
        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    
        event Transfer(address indexed from, address indexed to, uint256 value);
        event Approval(address indexed owner, address indexed spender, uint256 value);
    }
    
    interface IERC20Metadata is IERC20 {
        function name() external view returns (string memory);
        function symbol() external view returns (string memory);
        function decimals() external view returns (uint8);
    }
    
    contract ERC20 is Context, IERC20, IERC20Metadata {
        mapping(address => uint256) private _balances;
    
        mapping(address => mapping(address => uint256)) private _allowances;
    
        uint256 private _totalSupply;
    
        string private _name;
        string private _symbol;
    
        constructor(string memory name_, string memory symbol_) {
            _name = name_;
            _symbol = symbol_;
        }
    
        function name() public view virtual override returns (string memory) {
            return _name;
        }
    
        function symbol() public view virtual override returns (string memory) {
            return _symbol;
        }
    
        function decimals() public view virtual override returns (uint8) {
            return 18;
        }
    
        function totalSupply() public view virtual override returns (uint256) {
            return _totalSupply;
        }
    
        function balanceOf(address account) public view virtual override returns (uint256) {
            return _balances[account];
        }
    
        function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
            _transfer(_msgSender(), recipient, amount);
            return true;
        }
    
        function allowance(address owner, address spender) public view virtual override returns (uint256) {
            return _allowances[owner][spender];
        }
    
        function approve(address spender, uint256 amount) public virtual override returns (bool) {
            _approve(_msgSender(), spender, amount);
            return true;
        }
    
        function transferFrom(address sender,address recipient,uint256 amount) public virtual override returns (bool) {
            _transfer(sender, recipient, amount);
    
            uint256 currentAllowance = _allowances[sender][_msgSender()];
            require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
            
            _approve(sender, _msgSender(), currentAllowance - amount);
    
            return true;
        }
    
        function _transfer(address sender, address recipient, uint256 amount) internal virtual {
            require(sender != address(0), "ERC20: transfer from the zero address");
            require(recipient != address(0), "ERC20: transfer to the zero address");
    
            uint256 senderBalance = _balances[sender];
            require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
    
            _balances[sender] = senderBalance - amount;
            _balances[recipient] += amount;
    
            emit Transfer(sender, recipient, amount);
        }
    
        function _mint(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: mint to the zero address");
    
            _totalSupply += amount;
            _balances[account] += amount;
            emit Transfer(address(0), account, amount);
        }
    
        function _burn(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: burn from the zero address");
    
            uint256 accountBalance = _balances[account];
            require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
            
            _balances[account] = accountBalance - amount;
            _totalSupply -= amount;
    
            emit Transfer(account, address(0), amount);
        }
    
        function _approve(address owner, address spender, uint256 amount) internal virtual {
            require(owner != address(0), "ERC20: approve from the zero address");
            require(spender != address(0), "ERC20: approve to the zero address");
    
            _allowances[owner][spender] = amount;
            emit Approval(owner, spender, amount);
        }
    }
    
    contract Zeta is ERC20 {
        constructor(uint256 initialSupply, string memory name, string memory symbol) ERC20(name, symbol) {
            _mint(msg.sender, initialSupply * (10 ** uint256(decimals())));
        }
    }