MetaWalletNFT

Description:

Non-Fungible Token (NFT) contract following ERC721 standard.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/MetaWalletNFT.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
 * @title Omni NFT Wallet
 */

import "contracts/IERC721.sol";
import "contracts/utils/cryptography/ECDSA.sol";
import "contracts/RlpEncode.sol";

contract MetaWalletNFT {
    uint64 public constant MAX_UINT64 = 2 ** 64 - 1;
    uint128 public constant NONCE_TS_SHIFT = 1000000000000;

    address public owner;
    address public verifyAddress;
    uint64 public chainId;
    uint128 public nonceMax;

    uint256 public fees;

    mapping(uint128 => bytes32) public deposits;

    mapping(uint128 => bool) public usedNonces;

    event NftDeposit(uint128 nonce, uint256 tokenId, bytes contract_id, bytes receiver);

    modifier onlyOwner() {
        require(msg.sender == owner, "Only the owner can call this function.");
        _;
    }

    /**
     * @notice Withdraw NFT from the contract to the owner's address.
     * @dev Only callable by the owner.
     * @param nftAddress The address of the NFT contract to withdraw from.
     * @param tokenId The ID of the NFT to withdraw.
     */
    function withdrawNFT(address nftAddress, uint256 tokenId)
    public
    onlyOwner
    {
        IERC721(nftAddress).transferFrom(address(this), owner, tokenId);
    }

    /**
     * @notice Withdraw Ether from the contract to the owner's address.
     * @dev Only callable by the owner.
     * @param amount The amount of Ether to withdraw (in wei).
     */
    function withdrawEth(uint256 amount) public onlyOwner {
        (bool success,) = payable(owner).call{value: amount}("");
        require(success, "Transfer failed.");
    }

    /**
     * @notice Withdraw accumulated fees from the contract to the owner's address.
     * @dev Only callable by the owner. Resets the fees to zero after withdrawal.
     */
    function withdrawFees() public onlyOwner {
        (bool success,) = payable(owner).call{value: fees}("");
        require(success, "Transfer failed.");
        fees = 0;
    }

    /**
     * @notice Change ownership of the contract to a new address.
     * @dev Only callable by the current owner.
     * @param newOwner The address of the new owner.
     */
    function changeOwner(address newOwner) public onlyOwner {
        owner = newOwner;
    }

    /**
     * @notice Fallback function to receive Ether.
     */
    receive() external payable {}

    /**
     * @notice Initializes the MetaWalletNFT contract.
     * @param _verifyAddress The address used to verify signatures.
     * @param _chainId The chain ID of the blockchain network.
     */
    constructor(
        address _verifyAddress,
        uint64 _chainId
    ) payable {
        owner = msg.sender;
        verifyAddress = _verifyAddress;
        chainId = _chainId;
        fees = 0;
    }

    /**
     * @notice Change the address used for signature verification.
     * @dev Only callable by the owner.
     * @param _verifyAddress The new address to use for verifying signatures.
     */
    function changeVerifyAddress(address _verifyAddress) public onlyOwner {
        verifyAddress = _verifyAddress;
    }

    /**
     * @notice Execute a withdrawal from the contract to the specified receiver.
     * @param nonce The unique nonce associated with the withdrawal.
     * @param contract_id The address of the NFT contract.
     * @param receiver_id The address of the receiver.
     * @param tokenId The ID of the NFT to withdraw.
     * @param signature The signature proving authorization for the withdrawal.
     *
     * @dev Verifies the provided signature and ensures that the nonce is valid.
     *      Increments the fees by the amount of Ether sent with the transaction.
     */
    function withdraw(
        uint128 nonce,
        address contract_id,
        address receiver_id,
        uint256 tokenId,
        bytes memory signature
    ) public payable {
        uint128 nonce_ts = nonce / NONCE_TS_SHIFT;

        require(!usedNonces[nonce], "Nonce already used");
        require(
            verify(
                nonce,
                abi.encodePacked(contract_id),
                abi.encodePacked(receiver_id),
                tokenId,
                signature
            ),
            "Invalid signature"
        );
        usedNonces[nonce] = true;
        IERC721(contract_id).transferFrom(address(this), receiver_id, tokenId);
        fees += msg.value;
    }

    /**
     * @notice Deposit NFT into the contract on behalf of a receiver.
     * @param receiver The receiver's identifier (in bytes).
     * @param contract_id The address of the NFT contract.
     * @param tokenId The ID of the NFT to deposit.
     *
     * @dev Transfers NFT from the sender to the contract and records the deposit.
     *      Increments the fees by the amount of Ether sent with the transaction.
     */
    function deposit(
        bytes memory receiver,
        address contract_id,
        uint256 tokenId
    ) public payable {
        IERC721(contract_id).transferFrom(
            msg.sender,
            address(this),
            tokenId
        );
        uint128 nonce = uint128(block.timestamp) * NONCE_TS_SHIFT + nonceMax;
        emit NftDeposit(nonce, tokenId, abi.encodePacked(contract_id), receiver);

        bytes32 origMessageHash = getMessageHash(
            nonce,
            abi.encodePacked(contract_id),
            receiver,
            tokenId
        );

        deposits[nonce] = origMessageHash;
        nonceMax += 1;
        fees += msg.value;
    }

    /**
    * @notice Deposit multiple NFTs into the contract on behalf of a receiver.
    * @param receiver The receiver's identifier (in bytes).
    * @param contract_id The address of the NFT contract.
    * @param tokenIds An array of NFT IDs to deposit.
    * @dev Iterates through the array of token IDs and calls the deposit function for each.
    *      Increments the fees by the total amount of Ether sent with the transaction.
    */
     function multi_deposit(
        bytes memory receiver,
        address contract_id,
        uint256[] memory tokenIds
    ) public payable {
        require(tokenIds.length > 0, "No token IDs provided");
        for (uint256 i = 0; i < tokenIds.length; i++) {
            deposit(
                receiver,
                contract_id,
                tokenIds[i]
            );
        }
     }

    /**
     * @notice Verifies that the message hash can be signed by HOT Validators.
     * @param msg_hash The hash of the message to verify.
     * @param userPayload: (uint128 nonce, uint8 type_) encoded in ABI. Type_ = 0 for deposit, 1 for refund
     * @return True if the message hash can be signed; false otherwise.
     *
     * @dev Used by HOT Validators to verify messages before signing.
     */
    function hot_verify(
        bytes32 msg_hash,
        bytes memory _walletId,
        bytes memory userPayload,
        bytes memory _metadata
    ) public view returns (bool) {
        (uint128 nonce) = abi.decode(
            userPayload,
            (uint128)
        );
        require(deposits[nonce] == msg_hash, "Deposit hash mismatch");
        return true;
    }

    /**
     * @notice Verifies the signature of a message.
     * @param nonce The unique nonce associated with the message.
     * @param contract_id The encoded contract address.
     * @param receiver_id The encoded receiver address.
     * @param tokenId The ID of the NFT involved in the transaction.
     * @param signature The signature to verify.
     * @return True if the signature is valid; false otherwise.
     *
     * @dev Uses ECDSA to recover the signer's address and compares it with the verifyAddress.
     */
    function verify(
        uint128 nonce,
        bytes memory contract_id,
        bytes memory receiver_id,
        uint256 tokenId,
        bytes memory signature
    ) internal view returns (bool) {
        bytes32 messageHash = getMessageHash(
            nonce,
            contract_id,
            receiver_id,
            tokenId
        );
        return ECDSA.recover(messageHash, signature) == verifyAddress;
    }

    /**
     * @notice Generates the SHA-256 hash of the raw message data.
     * @param nonce The unique nonce associated with the message.
     * @param contract_id The encoded contract address.
     * @param receiver_id The encoded receiver address.
     * @param tokenId The ID of the NFT involved in the transaction.
     * @return The SHA-256 hash of the message data.
     */
    function getMessageHash(
        uint128 nonce,
        bytes memory contract_id,
        bytes memory receiver_id,
        uint256 tokenId
    ) public view returns (bytes32) {
        return sha256(getMessageRaw(nonce, contract_id, receiver_id, tokenId));
    }

    /**
     * @notice Constructs the raw data for a message.
     * @param nonce The unique nonce associated with the message.
     * @param contract_id The encoded contract address.
     * @param receiver_id The encoded receiver address.
     * @param tokenId The ID of the NFT involved in the transaction.
     * @return The RLP-encoded raw message data.
     */
    function getMessageRaw(
        uint128 nonce,
        bytes memory contract_id,
        bytes memory receiver_id,
        uint256 tokenId
    ) public view returns (bytes memory) {
        bytes[] memory rlpList = new bytes[](5);
        rlpList[0] = RLPEncode.encodeUint128(nonce, 16);
        rlpList[1] = RLPEncode.encodeUint64(chainId, 8);
        rlpList[2] = RLPEncode.encodeBytes(contract_id);
        rlpList[3] = RLPEncode.encodeBytes(receiver_id);
        rlpList[4] = RLPEncode.encodeBytes(abi.encodePacked(tokenId));

        return RLPEncode.encodeList(rlpList);
    }
} "
    },
    "contracts/IERC721.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title IERC721
 * @dev Interface for ERC721 tokens
 */
interface IERC721 {
    /**
     * @dev Transfers the ownership of a given token ID to another address
     * @param from current owner of the token
     * @param to address to receive the ownership of the given token ID
     * @param tokenId uint256 ID of the token to be transferred
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gets the approved address to take ownership of a given token ID
     * @param tokenId uint256 ID of the token to query the approval of
     * @return address currently approved for the given token ID
     */
    function getApproved(uint256 tokenId) external view returns (address);

    /**
     * @dev Approves another address to transfer the given token ID
     * @param to address to be approved for the given token ID
     * @param tokenId uint256 ID of the token to be approved
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Returns the number of tokens in the given owner's account
     * @param owner address of the owner to query
     * @return uint256 representing the amount of tokens owned by the passed address
     */
    function balanceOf(address owner) external view returns (uint256);

    /**
     * @dev Returns the owner of the given token ID
     * @param tokenId uint256 ID of the token to query the owner of
     * @return address currently marked as the owner of the given token ID
     */
    function ownerOf(uint256 tokenId) external view returns (address);

    /**
     * @dev Safely transfers the ownership of a given token ID to another address
     * @param from current owner of the token
     * @param to address to receive the ownership of the given token ID
     * @param tokenId uint256 ID of the token to be transferred
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Safely transfers the ownership of a given token ID to another address
     * @param from current owner of the token
     * @param to address to receive the ownership of the given token ID
     * @param tokenId uint256 ID of the token to be transferred
     * @param data bytes data to send along with a safe transfer check
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Returns the total amount of tokens stored by the contract
     * @return uint256 representing the total amount of tokens
     */
    function totalSupply() external view returns (uint256);
} "
    },
    "contracts/utils/cryptography/ECDSA.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        } else if (error == RecoverError.InvalidSignatureV) {
            revert("ECDSA: invalid signature 'v' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        // Check the signature length
        // - case 65: r,s,v signature (standard)
        // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else if (signature.length == 64) {
            bytes32 r;
            bytes32 vs;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                vs := mload(add(signature, 0x40))
            }
            return tryRecover(hash, r, vs);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address, RecoverError) {
        bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }
        if (v != 27 && v != 28) {
            return (address(0), RecoverError.InvalidSignatureV);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\
32", hash));
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\
", Strings.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
}

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0x00";
        }
        uint256 temp = value;
        uint256 length = 0;
        while (temp != 0) {
            length++;
            temp >>= 8;
        }
        return toHexString(value, length);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _HEX_SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }
} "
    },
    "contracts/RlpEncode.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

library RLPEncode {
  function encodeUint(uint256 _uint, uint8 byteLength) internal pure returns (bytes memory) {
        bytes memory b = new bytes(byteLength);
        for (uint8 i = 0; i < byteLength; i++) {
            b[byteLength - 1 - i] = bytes1(uint8(_uint >> (8 * i)));
        }
        return encodeBytes(b);
    }

    function encodeUint128(uint128 _uint, uint8 byteLength) internal pure returns (bytes memory) {
        return encodeUint(uint256(_uint), byteLength);
    }

    function encodeUint64(uint64 _uint, uint8 byteLength) internal pure returns (bytes memory) {
        return encodeUint(uint256(_uint), byteLength);
    }

    function encodeBytes(bytes memory input) internal pure returns (bytes memory) {
        uint256 length = input.length;

        if (length == 1 && uint8(input[0]) <= 0x7f) {
            // Если длина 1 и значение <= 0x7f, возвращаем как есть
            return input;
        } else if (length <= 55) {
            // Если длина <= 55, используем короткий формат
            bytes memory output = new bytes(1 + length);
            output[0] = bytes1(uint8(0x80 + length));
            for (uint256 i = 0; i < length; i++) {
                output[i + 1] = input[i];
            }
            return output;
        } else {
            // Если длина > 55, используем длинный формат
            uint256 tempLength = length;
            uint256 byteLength = 0;
            while (tempLength != 0) {
                byteLength++;
                tempLength >>= 8;
            }

            bytes memory output = new bytes(1 + byteLength + length);
            output[0] = bytes1(uint8(0xb7 + byteLength));
            // Исправлено: запись байтов длины в правильном порядке
            for (uint256 i = 0; i < byteLength; i++) {
                output[1 + byteLength - 1 - i] = bytes1(uint8(length >> (8 * i)));
            }
            // Копирование данных
            for (uint256 i = 0; i < length; i++) {
                output[1 + byteLength + i] = input[i];
            }
            return output;
        }
    }
    
    function encodeList(bytes[] memory _items) internal pure returns (bytes memory) {
        bytes memory concatenated;
        for (uint256 i = 0; i < _items.length; i++) {
            concatenated = concatenate(concatenated, _items[i]);
        }
        return concatenate(encodeLength(concatenated.length, 192), concatenated);
    }

    function encodeLength(uint256 _length, uint256 _offset) internal pure returns (bytes memory) {
        if (_length < 56) {
            return bytes(abi.encodePacked(uint8(_length + _offset)));
        } else {
            uint256 lenLen;
            uint256 i = 1;
            while (_length / i != 0) {
                lenLen++;
                i *= 256;
            }
            bytes memory b = bytes(abi.encodePacked(uint8(lenLen + _offset + 55)));
            for (i = (lenLen - 1) * 8; i > 0; i -= 8) {
                b = concatenate(b, bytes(abi.encodePacked(uint8(_length / (2 ** i)))));
            }
            return concatenate(b, bytes(abi.encodePacked(uint8(_length))));
        }
    }

    function concatenate(bytes memory a, bytes memory b) internal pure returns (bytes memory) {
        bytes memory result = new bytes(a.length + b.length);

        assembly {
            let resultPtr := add(result, 0x20)
            let aPtr := add(a, 0x20)
            let bPtr := add(b, 0x20)
            let aLength := mload(a)
            let bLength := mload(b)

            // Copy a to result
            for { let i := 0 } lt(i, aLength) { i := add(i, 0x20) } {
                mstore(add(resultPtr, i), mload(add(aPtr, i)))
            }

            // Copy b to result
            for { let i := 0 } lt(i, bLength) { i := add(i, 0x20) } {
                mstore(add(resultPtr, add(aLength, i)), mload(add(bPtr, i)))
            }
        }

        return result;
    }

}
"
    }
  },
  "settings": {
    "evmVersion": "istanbul",
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    }
  }
}}

Tags:
ERC721, NFT, Non-Fungible, Factory|addr:0xca9d62838044888e2f9acedc3c5ae5f19f3c14fd|verified:true|block:23629692|tx:0x95193143169467c4c4b7913bdb73c47503be430c15beaaae263080aa072a2f5e|first_check:1761232527

Submitted on: 2025-10-23 17:15:29

Comments

Log in to comment.

No comments yet.