// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.2) (utils/Base64.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides a set of functions to operate with Base64 strings.
*/
library Base64 {
/**
* @dev Base64 Encoding/Decoding Table
*/
string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* @dev Converts a `bytes` to its Bytes64 `string` representation.
*/
function encode(bytes memory data) internal pure returns (string memory) {
/**
* Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
* https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
*/
if (data.length == 0) return "";
// Loads the table into memory
string memory table = _TABLE;
// Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
// and split into 4 numbers of 6 bits.
// The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
// - `data.length + 2` -> Round up
// - `/ 3` -> Number of 3-bytes chunks
// - `4 *` -> 4 characters for each chunk
string memory result = new string(4 * ((data.length + 2) / 3));
/// @solidity memory-safe-assembly
assembly {
// Prepare the lookup table (skip the first "length" byte)
let tablePtr := add(table, 1)
// Prepare result pointer, jump over length
let resultPtr := add(result, 0x20)
let dataPtr := data
let endPtr := add(data, mload(data))
// In some cases, the last iteration will read bytes after the end of the data. We cache the value, and
// set it to zero to make sure no dirty bytes are read in that section.
let afterPtr := add(endPtr, 0x20)
let afterCache := mload(afterPtr)
mstore(afterPtr, 0x00)
// Run over the input, 3 bytes at a time
for {
} lt(dataPtr, endPtr) {
} {
// Advance 3 bytes
dataPtr := add(dataPtr, 3)
let input := mload(dataPtr)
// To write each character, shift the 3 byte (24 bits) chunk
// 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
// and apply logical AND with 0x3F to bitmask the least significant 6 bits.
// Use this as an index into the lookup table, mload an entire word
// so the desired character is in the least significant byte, and
// mstore8 this least significant byte into the result and continue.
mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
resultPtr := add(resultPtr, 1) // Advance
}
// Reset the value that was cached
mstore(afterPtr, afterCache)
// When data `bytes` is not exactly 3 bytes long
// it is padded with `=` characters at the end
switch mod(mload(data), 3)
case 1 {
mstore8(sub(resultPtr, 1), 0x3d)
mstore8(sub(resultPtr, 2), 0x3d)
}
case 2 {
mstore8(sub(resultPtr, 1), 0x3d)
}
}
return result;
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import {
RMRKAbstractEquippable
} from "@rmrk-team/evm-contracts/contracts/implementations/abstract/RMRKAbstractEquippable.sol";
import {
RMRKImplementationBase
} from "@rmrk-team/evm-contracts/contracts/implementations/utils/RMRKImplementationBase.sol";
import {
RMRKTokenHolder
} from "@rmrk-team/evm-contracts/contracts/RMRK/extension/tokenHolder/RMRKTokenHolder.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Base64} from "@openzeppelin/contracts/utils/Base64.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {IChunkies} from "./IChunkies.sol";
error OnlyNFTOwnerCanTransferTokensFromIt();
error LengthMismatch();
contract Chunkies is
RMRKAbstractEquippable,
RMRKTokenHolder /** , IChunkies */
{
using Strings for uint256;
// Events
/**
* @notice From ERC4906 This event emits when the metadata of a token is changed.
* So that the third-party platforms such as NFT market could
* get notified when the metadata of a token is changed.
*/
event MetadataUpdate(uint256 _tokenId);
// Variables
mapping(address => bool) private _autoAcceptCollection;
mapping(uint64 assetId => bool isOthala) private _othalaAssets;
mapping(uint64 assetId => bool isDead) private _deadAssets;
string private _baseAnimationURI;
string private _baseImageURI;
string private _othalaDescription;
string private _regularDescription;
string private _addressAsString;
mapping(uint256 tokenId => uint256 timestamp) private _lastEquipUpdate;
uint256 private immutable _deployedTimestamp = block.timestamp;
// Constructor
constructor(
string memory collectionMetadata,
address royaltyRecipient,
uint16 royaltyPercentageBps,
string memory othalaDescription,
string memory regularDescription
)
RMRKImplementationBase(
"Chunkies",
"CHNK",
collectionMetadata,
2 ** 256 - 1, // Infinite supply
royaltyRecipient,
royaltyPercentageBps
)
{
_othalaDescription = othalaDescription;
_regularDescription = regularDescription;
}
function mintWithAsset(
address to,
uint256 numToMint,
uint64 assetId
) external onlyOwnerOrContributor returns (uint256 firstTokenId) {
(uint256 nextToken, uint256 totalSupplyOffset) = _prepareMint(
numToMint
);
for (uint256 tokenId = nextToken; tokenId < totalSupplyOffset; ) {
_safeMint(to, tokenId, "");
_addAssetToToken(tokenId, assetId, 0);
_acceptAsset(tokenId, 0, assetId);
unchecked {
++tokenId;
}
}
firstTokenId = nextToken;
}
// ------------------ TOKEN URI ------------------
function setBaseAnimationURI(
string memory baseAnimationURI
) external onlyOwnerOrContributor {
_baseAnimationURI = baseAnimationURI;
}
function setBaseImageURI(
string memory baseImageURI
) external onlyOwnerOrContributor {
_baseImageURI = baseImageURI;
}
function tokenURI(uint256 tokenId) external view returns (string memory) {
_requireMinted(tokenId);
string memory tokenID = tokenId.toString();
uint64 mainAssetId = getActiveAssets(tokenId)[0];
(bool isOthala_, ) = getAssetConfiguration(mainAssetId);
string memory json = string(
abi.encodePacked(
'{\n\t"name": "',
_deadAssets[mainAssetId] ? "Dead " : "",
isOthala_ ? "Othala " : "",
"Chunky #",
tokenID,
'",\n\t"description": "',
isOthala_ ? _othalaDescription : _regularDescription,
'",\n\t"external_url": "https://skybreach.app/',
'",\n\t"image":',
_buildImageURI(tokenId),
',\n\t"animation_url": "',
_baseAnimationURI,
tokenID,
'"\n}'
)
); // format the json
return
string(
abi.encodePacked(
"data:application/json;base64,",
Base64.encode(bytes(json))
)
); // return the full concatenated string
}
// ------------------ ASSETS ------------------
function getAssetConfiguration(
uint64 assetId
) public view returns (bool isOthala, bool isDead) {
isOthala = _othalaAssets[assetId];
isDead = _deadAssets[assetId];
}
function configureAsset(
uint64 assetId,
bool isOthala_,
bool isDead_
) external onlyOwnerOrContributor {
_othalaAssets[assetId] = isOthala_;
_deadAssets[assetId] = isDead_;
}
function replaceMainAsset(
uint256 tokenId,
uint64 newAssetId
) external onlyOwnerOrContributor {
uint64 currentAssetId = _getMainAsset(tokenId);
_addAssetToToken(tokenId, newAssetId, currentAssetId);
_acceptAsset(tokenId, _pendingAssets[tokenId].length - 1, newAssetId);
emit MetadataUpdate(tokenId);
}
function _getMainAsset(uint256 tokenId) internal view returns (uint64) {
return getActiveAssets(tokenId)[0];
}
// ------------------ ERC20 ------------------
function transferHeldERC20FromToken(
address erc20Contract,
uint256 tokenHolderId,
address to,
uint256 amount,
bytes memory data
) external {
if (_msgSender() != ownerOf(tokenHolderId)) {
revert OnlyNFTOwnerCanTransferTokensFromIt();
}
_transferHeldERC20FromToken(
erc20Contract,
tokenHolderId,
to,
amount,
data
);
}
// ------------------ MISC ------------------
/**
* @inheritdoc IERC165
*/
function supportsInterface(
bytes4 interfaceId
)
public
view
override(RMRKTokenHolder, RMRKAbstractEquippable)
returns (bool)
{
return
RMRKAbstractEquippable.supportsInterface(interfaceId) ||
RMRKTokenHolder.supportsInterface(interfaceId);
}
function setAutoAcceptCollection(
address collection,
bool autoAccept
) external onlyOwnerOrContributor {
_autoAcceptCollection[collection] = autoAccept;
}
function addSlotPartToAsset(
uint64 assetId,
uint64 slotPartId
) external onlyOwnerOrContributor {
_partIds[assetId].push(slotPartId);
}
// ------------------ INTERNAL HELPERS ------------------
function _buildImageURI(
uint256 tokenId
) internal view returns (string memory) {
string memory tokenIdStr = tokenId.toString();
uint256 lastEquippedInt = _lastEquipUpdate[tokenId];
uint256 mainAssetId = _getMainAsset(tokenId);
string memory assetIdStr = mainAssetId.toString();
return
string(
abi.encodePacked(
'"',
_baseImageURI,
(
lastEquippedInt == 0
? _deployedTimestamp
: lastEquippedInt
).toString(),
"/",
Strings.toHexString(uint256(uint160(address(this))), 20),
"/",
tokenIdStr,
"/",
assetIdStr,
'.webp"'
)
);
}
function _beforeEquip(IntakeEquip memory data) internal override {
_storeLastEquipUpdate(data.tokenId);
// emit MetadataUpdate(data.tokenId);
}
function _beforeUnequip(uint256 tokenId, uint64, uint64) internal override {
_storeLastEquipUpdate(tokenId);
// emit MetadataUpdate(tokenId);
}
function _storeLastEquipUpdate(uint256 tokenId) private {
_lastEquipUpdate[tokenId] = block.timestamp;
}
function _afterAddChild(
uint256 tokenId,
address childAddress,
uint256 childId,
bytes memory
) internal override {
// Auto accept children if they are from known collections
if (_autoAcceptCollection[childAddress]) {
_acceptChild(
tokenId,
_pendingChildren[tokenId].length - 1,
childAddress,
childId
);
}
}
function _afterAddAssetToToken(
uint256 tokenId,
uint64 assetId,
uint64 replacesAssetWithId
) internal override {
// Remove auto accept from AbstractEquippable. We handle it here.
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @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;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import {
IERC5773
} from "@rmrk-team/evm-contracts/contracts/RMRK/multiasset/IERC5773.sol";
import {
IERC7401
} from "@rmrk-team/evm-contracts/contracts/RMRK/nestable/IERC7401.sol";
import {
IERC6220
} from "@rmrk-team/evm-contracts/contracts/RMRK/equippable/IERC6220.sol";
interface IChunkies is IERC5773, IERC7401, IERC6220 {
function mintWithAsset(
address to,
uint256 numToMint,
uint64 assetId
) external returns (uint256);
function replaceMainAsset(uint256 tokenId, uint64 newAssetId) external;
function getAssetConfiguration(
uint64 assetId
) external view returns (bool isOthala_, bool isDead_);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @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 value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` 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 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC2981.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../utils/introspection/IERC165.sol";
/**
* @dev Interface for the NFT Royalty Standard.
*
* A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
* support for royalty payments across all NFT marketplaces and ecosystem participants.
*/
interface IERC2981 is IERC165 {
/**
* @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
* exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
*/
function royaltyInfo(
uint256 tokenId,
uint256 salePrice
) external view returns (address receiver, uint256 royaltyAmount);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
/**
* @title IERC5773
* @author RMRK team
* @notice Interface smart contract of the RMRK multi asset module.
*/
interface IERC5773 is IERC165 {
/**
* @notice Used to notify listeners that an asset object is initialized at `assetId`.
* @param assetId ID of the asset that was initialized
*/
event AssetSet(uint64 indexed assetId);
/**
* @notice Used to notify listeners that an asset object at `assetId` is added to token's pending asset
* array.
* @param tokenIds An array of token IDs that received a new pending asset
* @param assetId ID of the asset that has been added to the token's pending assets array
* @param replacesId ID of the asset that would be replaced
*/
event AssetAddedToTokens(
uint256[] tokenIds,
uint64 indexed assetId,
uint64 indexed replacesId
);
/**
* @notice Used to notify listeners that an asset object at `assetId` is accepted by the token and migrated
* from token's pending assets array to active assets array of the token.
* @param tokenId ID of the token that had a new asset accepted
* @param assetId ID of the asset that was accepted
* @param replacesId ID of the asset that was replaced
*/
event AssetAccepted(
uint256 indexed tokenId,
uint64 indexed assetId,
uint64 indexed replacesId
);
/**
* @notice Used to notify listeners that an asset object at `assetId` is rejected from token and is dropped
* from the pending assets array of the token.
* @param tokenId ID of the token that had an asset rejected
* @param assetId ID of the asset that was rejected
*/
event AssetRejected(uint256 indexed tokenId, uint64 indexed assetId);
/**
* @notice Used to notify listeners that token's prioritiy array is reordered.
* @param tokenId ID of the token that had the asset priority array updated
*/
event AssetPrioritySet(uint256 indexed tokenId);
/**
* @notice Used to notify listeners that owner has granted an approval to the user to manage the assets of a
* given token.
* @dev Approvals must be cleared on transfer
* @param owner Address of the account that has granted the approval for all token's assets
* @param approved Address of the account that has been granted approval to manage the token's assets
* @param tokenId ID of the token on which the approval was granted
*/
event ApprovalForAssets(
address indexed owner,
address indexed approved,
uint256 indexed tokenId
);
/**
* @notice Used to notify listeners that owner has granted approval to the user to manage assets of all of their
* tokens.
* @param owner Address of the account that has granted the approval for all assets on all of their tokens
* @param operator Address of the account that has been granted the approval to manage the token's assets on all of
* the tokens
* @param approved Boolean value signifying whether the permission has been granted (`true`) or revoked (`false`)
*/
event ApprovalForAllForAssets(
address indexed owner,
address indexed operator,
bool approved
);
/**
* @notice Accepts an asset at from the pending array of given token.
* @dev Migrates the asset from the token's pending asset array to the token's active asset array.
* @dev Active assets cannot be removed by anyone, but can be replaced by a new asset.
* @dev Requirements:
*
* - The caller must own the token or be approved to manage the token's assets
* - `tokenId` must exist.
* - `index` must be in range of the length of the pending asset array.
* @dev Emits an {AssetAccepted} event.
* @param tokenId ID of the token for which to accept the pending asset
* @param index Index of the asset in the pending array to accept
* @param assetId ID of the asset expected to be in the index
*/
function acceptAsset(
uint256 tokenId,
uint256 index,
uint64 assetId
) external;
/**
* @notice Rejects an asset from the pending array of given token.
* @dev Removes the asset from the token's pending asset array.
* @dev Requirements:
*
* - The caller must own the token or be approved to manage the token's assets
* - `tokenId` must exist.
* - `index` must be in range of the length of the pending asset array.
* @dev Emits a {AssetRejected} event.
* @param tokenId ID of the token that the asset is being rejected from
* @param index Index of the asset in the pending array to be rejected
* @param assetId ID of the asset expected to be in the index
*/
function rejectAsset(
uint256 tokenId,
uint256 index,
uint64 assetId
) external;
/**
* @notice Rejects all assets from the pending array of a given token.
* @dev Effecitvely deletes the pending array.
* @dev Requirements:
*
* - The caller must own the token or be approved to manage the token's assets
* - `tokenId` must exist.
* @dev Emits a {AssetRejected} event with assetId = 0.
* @param tokenId ID of the token of which to clear the pending array.
* @param maxRejections Maximum number of expected assets to reject, used to prevent from rejecting assets which
* arrive just before this operation.
*/
function rejectAllAssets(uint256 tokenId, uint256 maxRejections) external;
/**
* @notice Sets a new priority array for a given token.
* @dev The priority array is a non-sequential list of `uint64`s, where the lowest value is considered highest
* priority.
* @dev Value `0` of a priority is a special case equivalent to unitialized.
* @dev Requirements:
*
* - The caller must own the token or be approved to manage the token's assets
* - `tokenId` must exist.
* - The length of `priorities` must be equal the length of the active assets array.
* @dev Emits a {AssetPrioritySet} event.
* @param tokenId ID of the token to set the priorities for
* @param priorities An array of priorities of active assets. The succesion of items in the priorities array
* matches that of the succesion of items in the active array
*/
function setPriority(
uint256 tokenId,
uint64[] calldata priorities
) external;
/**
* @notice Used to retrieve IDs of the active assets of given token.
* @dev Asset data is stored by reference, in order to access the data corresponding to the ID, call
* `getAssetMetadata(tokenId, assetId)`.
* @dev You can safely get 10k
* @param tokenId ID of the token to retrieve the IDs of the active assets
* @return assetIds An array of active asset IDs of the given token
*/
function getActiveAssets(
uint256 tokenId
) external view returns (uint64[] memory assetIds);
/**
* @notice Used to retrieve IDs of the pending assets of given token.
* @dev Asset data is stored by reference, in order to access the data corresponding to the ID, call
* `getAssetMetadata(tokenId, assetId)`.
* @param tokenId ID of the token to retrieve the IDs of the pending assets
* @return assetIds An array of pending asset IDs of the given token
*/
function getPendingAssets(
uint256 tokenId
) external view returns (uint64[] memory assetIds);
/**
* @notice Used to retrieve the priorities of the active resoources of a given token.
* @dev Asset priorities are a non-sequential array of uint64 values with an array size equal to active asset
* priorites.
* @param tokenId ID of the token for which to retrieve the priorities of the active assets
* @return priorities An array of priorities of the active assets of the given token
*/
function getActiveAssetPriorities(
uint256 tokenId
) external view returns (uint64[] memory priorities);
/**
* @notice Used to retrieve the asset that will be replaced if a given asset from the token's pending array
* is accepted.
* @dev Asset data is stored by reference, in order to access the data corresponding to the ID, call
* `getAssetMetadata(tokenId, assetId)`.
* @param tokenId ID of the token to check
* @param newAssetId ID of the pending asset which will be accepted
* @return replacesAssetWithId ID of the asset which will be replaced
*/
function getAssetReplacements(
uint256 tokenId,
uint64 newAssetId
) external view returns (uint64 replacesAssetWithId);
/**
* @notice Used to fetch the asset metadata of the specified token's active asset with the given index.
* @dev Assets are stored by reference mapping `_assets[assetId]`.
* @dev Can be overriden to implement enumerate, fallback or other custom logic.
* @param tokenId ID of the token from which to retrieve the asset metadata
* @param assetId Asset Id, must be in the active assets array
* @return metadata The metadata of the asset belonging to the specified index in the token's active assets
* array
*/
function getAssetMetadata(
uint256 tokenId,
uint64 assetId
) external view returns (string memory metadata);
// Approvals
/**
* @notice Used to grant permission to the user to manage token's assets.
* @dev This differs from transfer approvals, as approvals are not cleared when the approved party accepts or
* rejects an asset, or sets asset priorities. This approval is cleared on token transfer.
* @dev Only a single account can be approved at a time, so approving the `0x0` address clears previous approvals.
* @dev Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
* @dev Emits an {ApprovalForAssets} event.
* @param to Address of the account to grant the approval to
* @param tokenId ID of the token for which the approval to manage the assets is granted
*/
function approveForAssets(address to, uint256 tokenId) external;
/**
* @notice Used to retrieve the address of the account approved to manage assets of a given token.
* @dev Requirements:
*
* - `tokenId` must exist.
* @param tokenId ID of the token for which to retrieve the approved address
* @return approved Address of the account that is approved to manage the specified token's assets
*/
function getApprovedForAssets(
uint256 tokenId
) external view returns (address approved);
/**
* @notice Used to add or remove an operator of assets for the caller.
* @dev Operators can call {acceptAsset}, {rejectAsset}, {rejectAllAssets} or {setPriority} for any token
* owned by the caller.
* @dev Requirements:
*
* - The `operator` cannot be the caller.
* @dev Emits an {ApprovalForAllForAssets} event.
* @param operator Address of the account to which the operator role is granted or revoked from
* @param approved The boolean value indicating whether the operator role is being granted (`true`) or revoked
* (`false`)
*/
function setApprovalForAllForAssets(
address operator,
bool approved
) external;
/**
* @notice Used to check whether the address has been granted the operator role by a given address or not.
* @dev See {setApprovalForAllForAssets}.
* @param owner Address of the account that we are checking for whether it has granted the operator role
* @param operator Address of the account that we are checking whether it has the operator role or not
* @return isApproved A boolean value indicating whether the account we are checking has been granted the operator role
*/
function isApprovedForAllForAssets(
address owner,
address operator
) external view returns (bool isApproved);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import {IERC5773} from "../multiasset/IERC5773.sol";
/**
* @title IERC6220
* @author RMRK team
* @notice Interface smart contract of the RMRK equippable module.
*/
interface IERC6220 is IERC5773 {
/**
* @notice Used to store the core structure of the `Equippable` RMRK lego.
* @return assetId The ID of the asset equipping a child
* @return childAssetId The ID of the asset used as equipment
* @return childId The ID of token that is equipped
* @return childEquippableAddress Address of the collection to which the child asset belongs to
*/
struct Equipment {
uint64 assetId;
uint64 childAssetId;
uint256 childId;
address childEquippableAddress;
}
/**
* @notice Used to provide a struct for inputing equip data.
* @dev Only used for input and not storage of data.
* @return tokenId ID of the token we are managing
* @return childIndex Index of a child in the list of token's active children
* @return assetId ID of the asset that we are equipping into
* @return slotPartId ID of the slot part that we are using to equip
* @return childAssetId ID of the asset that we are equipping
*/
struct IntakeEquip {
uint256 tokenId;
uint256 childIndex;
uint64 assetId;
uint64 slotPartId;
uint64 childAssetId;
}
/**
* @notice Used to notify listeners that a child's asset has been equipped into one of its parent assets.
* @param tokenId ID of the token that had an asset equipped
* @param assetId ID of the asset associated with the token we are equipping into
* @param slotPartId ID of the slot we are using to equip
* @param childId ID of the child token we are equipping into the slot
* @param childAddress Address of the child token's collection
* @param childAssetId ID of the asset associated with the token we are equipping
*/
event ChildAssetEquipped(
uint256 indexed tokenId,
uint64 indexed assetId,
uint64 indexed slotPartId,
uint256 childId,
address childAddress,
uint64 childAssetId
);
/**
* @notice Used to notify listeners that a child's asset has been unequipped from one of its parent assets.
* @param tokenId ID of the token that had an asset unequipped
* @param assetId ID of the asset associated with the token we are unequipping out of
* @param slotPartId ID of the slot we are unequipping from
* @param childId ID of the token being unequipped
* @param childAddress Address of the collection that a token that is being unequipped belongs to
* @param childAssetId ID of the asset associated with the token we are unequipping
*/
event ChildAssetUnequipped(
uint256 indexed tokenId,
uint64 indexed assetId,
uint64 indexed slotPartId,
uint256 childId,
address childAddress,
uint64 childAssetId
);
/**
* @notice Used to notify listeners that the assets belonging to a `equippableGroupId` have been marked as
* equippable into a given slot and parent
* @param equippableGroupId ID of the equippable group being marked as equippable into the slot associated with
* `slotPartId` of the `parentAddress` collection
* @param slotPartId ID of the slot part of the catalog into which the parts belonging to the equippable group
* associated with `equippableGroupId` can be equipped
* @param parentAddress Address of the collection into which the parts belonging to `equippableGroupId` can be
* equipped
*/
event ValidParentEquippableGroupIdSet(
uint64 indexed equippableGroupId,
uint64 indexed slotPartId,
address parentAddress
);
/**
* @notice Used to equip a child into a token.
* @dev The `IntakeEquip` stuct contains the following data:
* [
* tokenId,
* childIndex,
* assetId,
* slotPartId,
* childAssetId
* ]
* @param data An `IntakeEquip` struct specifying the equip data
*/
function equip(IntakeEquip memory data) external;
/**
* @notice Used to unequip child from parent token.
* @dev This can only be called by the owner of the token or by an account that has been granted permission to
* manage the given token by the current owner.
* @param tokenId ID of the parent from which the child is being unequipped
* @param assetId ID of the parent's asset that contains the `Slot` into which the child is equipped
* @param slotPartId ID of the `Slot` from which to unequip the child
*/
function unequip(
uint256 tokenId,
uint64 assetId,
uint64 slotPartId
) external;
/**
* @notice Used to check whether the token has a given child equipped.
* @dev This is used to prevent from transferring a child that is equipped.
* @param tokenId ID of the parent token for which we are querying for
* @param childAddress Address of the child token's smart contract
* @param childId ID of the child token
* @return isEquipped A boolean value indicating whether the child token is equipped into the given token or not
*/
function isChildEquipped(
uint256 tokenId,
address childAddress,
uint256 childId
) external view returns (bool isEquipped);
/**
* @notice Used to verify whether a token can be equipped into a given parent's slot.
* @param parent Address of the parent token's smart contract
* @param tokenId ID of the token we want to equip
* @param assetId ID of the asset associated with the token we want to equip
* @param slotId ID of the slot that we want to equip the token into
* @return canBeEquipped A boolean indicating whether the token with the given asset can be equipped into the desired slot
*/
function canTokenBeEquippedWithAssetIntoSlot(
address parent,
uint256 tokenId,
uint64 assetId,
uint64 slotId
) external view returns (bool canBeEquipped);
/**
* @notice Used to get the Equipment object equipped into the specified slot of the desired token.
* @dev The `Equipment` struct consists of the following data:
* [
* assetId,
* childAssetId,
* childId,
* childEquippableAddress
* ]
* @param tokenId ID of the token for which we are retrieving the equipped object
* @param targetCatalogAddress Address of the `Catalog` associated with the `Slot` part of the token
* @param slotPartId ID of the `Slot` part that we are checking for equipped objects
* @return equipment The `Equipment` struct containing data about the equipped object
*/
function getEquipment(
uint256 tokenId,
address targetCatalogAddress,
uint64 slotPartId
) external view returns (Equipment memory equipment);
/**
* @notice Used to get the asset and equippable data associated with given `assetId`.
* @param tokenId ID of the token for which to retrieve the asset
* @param assetId ID of the asset of which we are retrieving
* @return metadataURI The metadata URI of the asset
* @return equippableGroupId ID of the equippable group this asset belongs to
* @return catalogAddress The address of the catalog the part belongs to
* @return partIds An array of IDs of parts included in the asset
*/
function getAssetAndEquippableData(
uint256 tokenId,
uint64 assetId
)
external
view
returns (
string memory metadataURI,
uint64 equippableGroupId,
address catalogAddress,
uint64[] memory partIds
);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.20;
import {IERC165} from "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Metadata.sol)
pragma solidity ^0.8.20;
import {IERC721} from "../IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/
interface IERC721Metadata is IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.20;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be
* reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
/**
* @title IERC7401
* @author RMRK team
* @notice Interface smart contract of the RMRK nestable module.
*/
interface IERC7401 is IERC165 {
/**
* @notice The core struct of RMRK ownership.
* @dev The `DirectOwner` struct is used to store information of the next immediate owner, be it the parent token or
* the externally owned account.
* @dev If the token is owned by the externally owned account, the `tokenId` should equal `0`.
* @param tokenId ID of the parent token
* @param ownerAddress Address of the owner of the token. If the owner is another token, then the address should be
* the one of the parent token's collection smart contract. If the owner is externally owned account, the address
* should be the address of this account
* @param isNft A boolean value signifying whether the token is owned by another token (`true`) or by an externally
* owned account (`false`)
*/
struct DirectOwner {
uint256 tokenId;
address ownerAddress;
}
/**
* @notice Used to notify listeners that the token is being transferred.
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
* @param from Address of the previous immediate owner, which is a smart contract if the token was nested.
* @param to Address of the new immediate owner, which is a smart contract if the token is being nested.
* @param fromTokenId ID of the previous parent token. If the token was not nested before, the value should be `0`
* @param toTokenId ID of the new parent token. If the token is not being nested, the value should be `0`
* @param tokenId ID of the token being transferred
*/
event NestTransfer(
address indexed from,
address indexed to,
uint256 fromTokenId,
uint256 toTokenId,
uint256 indexed tokenId
);
/**
* @notice Used to notify listeners that a new token has been added to a given token's pending children array.
* @dev Emitted when a child NFT is added to a token's pending array.
* @param tokenId ID of the token that received a new pending child token
* @param childIndex Index of the proposed child token in the parent token's pending children array
* @param childAddress Address of the proposed child token's collection smart contract
* @param childId ID of the child token in the child token's collection smart contract
*/
event ChildProposed(
uint256 indexed tokenId,
uint256 childIndex,
address indexed childAddress,
uint256 indexed childId
);
/**
* @notice Used to notify listeners that a new child token was accepted by the parent token.
* @dev Emitted when a parent token accepts a token from its pending array, migrating it to the active array.
* @param tokenId ID of the token that accepted a new child token
* @param childIndex Index of the newly accepted child token in the parent token's active children array
* @param childAddress Address of the child token's collection smart contract
* @param childId ID of the child token in the child token's collection smart contract
*/
event ChildAccepted(
uint256 indexed tokenId,
uint256 childIndex,
address indexed childAddress,
uint256 indexed childId
);
/**
* @notice Used to notify listeners that all pending child tokens of a given token have been rejected.
* @dev Emitted when a token removes all a child tokens from its pending array.
* @param tokenId ID of the token that rejected all of the pending children
*/
event AllChildrenRejected(uint256 indexed tokenId);
/**
* @notice Used to notify listeners a child token has been transferred from parent token.
* @dev Emitted when a token transfers a child from itself, transferring ownership to the root owner.
* @param tokenId ID of the token that transferred a child token
* @param childIndex Index of a child in the array from which it is being transferred
* @param childAddress Address of the child token's collection smart contract
* @param childId ID of the child token in the child token's collection smart contract
* @param fromPending A boolean value signifying whether the token was in the pending child tokens array (`true`) or
* in the active child tokens array (`false`)
* @param toZero A boolean value signifying whether the token is being transferred to the `0x0` address (`true`) or
* not (`false`)
*/
event ChildTransferred(
uint256 indexed tokenId,
uint256 childIndex,
address indexed childAddress,
uint256 indexed childId,
bool fromPending,
bool toZero
);
/**
* @notice The core child token struct, holding the information about the child tokens.
* @return tokenId ID of the child token in the child token's collection smart contract
* @return contractAddress Address of the child token's smart contract
*/
struct Child {
uint256 tokenId;
address contractAddress;
}
/**
* @notice Used to retrieve the *root* owner of a given token.
* @dev The *root* owner of the token is an externally owned account (EOA). If the given token is child of another
* NFT, this will return an EOA address. Otherwise, if the token is owned by an EOA, this EOA will be returned.
* @param tokenId ID of the token for which the *root* owner has been retrieved
* @return owner_ The *root* owner of the token
*/
function ownerOf(uint256 tokenId) external view returns (address owner_);
/**
* @notice Used to retrieve the immediate owner of the given token.
* @dev If the immediate owner is another token, the address returned will be the parent token's collection address.
* @param tokenId ID of the token for which the RMRK owner is being retrieved
* @return owner Address of the given token's owner
* @return parentId The ID of the parent token. Should be `0` if the owner is an externally owned account
* @return isNFT The boolean value signifying whether the owner is an NFT or not
*/
function directOwnerOf(
uint256 tokenId
) external view returns (address owner, uint256 parentId, bool isNFT);
/**
* @notice Used to burn a given token.
* @dev When a token is burned, all of its child tokens are recursively burned as well.
* @dev When specifying the maximum recursive burns, the execution will be reverted if there are more children to be
* burned.
* @dev Setting the `maxRecursiveBurn` value to 0 will only attempt to burn the specified token and revert if there
* are any child tokens present.
* @dev The approvals are cleared when the token is burned.
* @dev Requirements:
*
* - `tokenId` must exist.
* @dev Emits a {Transfer} event.
* @param tokenId ID of the token to burn
* @param maxRecursiveBurns Maximum number of tokens to recursively burn
* @return burnedChildren Number of recursively burned children
*/
function burn(
uint256 tokenId,
uint256 maxRecursiveBurns
) external returns (uint256 burnedChildren);
/**
* @notice Used to add a child token to a given parent token.
* @dev This adds the child token into the given parent token's pending child tokens array.
* @dev Requirements:
*
* - `directOwnerOf` on the child contract must resolve to the called contract.
* - the pending array of the parent contract must not be full.
* @param parentId ID of the parent token to receive the new child token
* @param childId ID of the new proposed child token
* @param data Additional data with no specified format
*/
function addChild(
uint256 parentId,
uint256 childId,
bytes memory data
) external;
/**
* @notice Used to accept a pending child token for a given parent token.
* @dev This moves the child token from parent token's pending child tokens array into the active child tokens
* array.
* @param parentId ID of the parent token for which the child token is being accepted
* @param childIndex Index of a child tokem in the given parent's pending children array
* @param childAddress Address of the collection smart contract of the child token expected to be located at the
* specified index of the given parent token's pending children array
* @param childId ID of the child token expected to be located at the specified index of the given parent token's
* pending children array
*/
function acceptChild(
uint256 parentId,
uint256 childIndex,
address childAddress,
uint256 childId
) external;
/**
* @notice Used to reject all pending children of a given parent token.
* @dev Removes the children from the pending array mapping.
* @dev This does not update the ownership storage data on children. If necessary, ownership can be reclaimed by the
* rootOwner of the previous parent.
* @dev Requirements:
*
* Requirements:
*
* - `parentId` must exist
* @param parentId ID of the parent token for which to reject all of the pending tokens.
* @param maxRejections Maximum number of expected children to reject, used to prevent from rejecting children which
* arrive just before this operation.
*/
function rejectAllChildren(
uint256 parentId,
uint256 maxRejections
) external;
/**
* @notice Used to transfer a child token from a given parent token.
* @dev When transferring a child token, the owner of the token is set to `to`, or is not updated in the event of
* `to` being the `0x0` address.
* @param tokenId ID of the parent token from which the child token is being transferred
* @param to Address to which to transfer the token to
* @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token)
* @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or
* pending array)
* @param childAddress Address of the child token's collection smart contract.
* @param childId ID of the child token in its own collection smart contract.
* @param isPending A boolean value indicating whether the child token being transferred is in the pending array of
* the parent token (`true`) or in the active array (`false`)
* @param data Additional data with no specified format, sent in call to `_to`
*/
function transferChild(
uint256 tokenId,
address to,
uint256 destinationId,
uint256 childIndex,
address childAddress,
uint256 childId,
bool isPending,
bytes memory data
) external;
/**
* @notice Used to retrieve the active child tokens of a given parent token.
* @dev Returns array of Child structs existing for parent token.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @param parentId ID of the parent token for which to retrieve the active child tokens
* @return children An array of Child structs containing the parent token's active child tokens
*/
function childrenOf(
uint256 parentId
) external view returns (Child[] memory children);
/**
* @notice Used to retrieve the pending child tokens of a given parent token.
* @dev Returns array of pending Child structs existing for given parent.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @param parentId ID of the parent token for which to retrieve the pending child tokens
* @return children An array of Child structs containing the parent token's pending child tokens
*/
function pendingChildrenOf(
uint256 parentId
) external view returns (Child[] memory children);
/**
* @notice Used to retrieve a specific active child token for a given parent token.
* @dev Returns a single Child struct locating at `index` of parent token's active child tokens array.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @param parentId ID of the parent token for which the child is being retrieved
* @param index Index of the child token in the parent token's active child tokens array
* @return child A Child struct containing data about the specified child
*/
function childOf(
uint256 parentId,
uint256 index
) external view returns (Child memory child);
/**
* @notice Used to retrieve a specific pending child token from a given parent token.
* @dev Returns a single Child struct locating at `index` of parent token's active child tokens array.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @param parentId ID of the parent token for which the pending child token is being retrieved
* @param index Index of the child token in the parent token's pending child tokens array
* @return child A Child struct containting data about the specified child
*/
function pendingChildOf(
uint256 parentId,
uint256 index
) external view returns (Child memory child);
/**
* @notice Used to transfer the token into another token.
* @param from Address of the direct owner of the token to be transferred
* @param to Address of the receiving token's collection smart contract
* @param tokenId ID of the token being transferred
* @param destinationId ID of the token to receive the token being transferred
* @param data Additional data with no specified format, sent in the addChild call
*/
function nestTransferFrom(
address from,
address to,
uint256 tokenId,
uint256 destinationId,
bytes memory data
) external;
}
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.21;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
interface IERC7590 is IERC165 {
/**
* @notice Used to notify listeners that the token received ERC-20 tokens.
* @param erc20Contract The address of the ERC-20 smart contract
* @param toTokenId The ID of the token receiving the ERC-20 tokens
* @param from The address of the account from which the tokens are being transferred
* @param amount The number of ERC-20 tokens received
*/
event ReceivedERC20(
address indexed erc20Contract,
uint256 indexed toTokenId,
address indexed from,
uint256 amount
);
/**
* @notice Used to notify the listeners that the ERC-20 tokens have been transferred.
* @param erc20Contract The address of the ERC-20 smart contract
* @param fromTokenId The ID of the token from which the ERC-20 tokens have been transferred
* @param to The address receiving the ERC-20 tokens
* @param amount The number of ERC-20 tokens transferred
*/
event TransferredERC20(
address indexed erc20Contract,
uint256 indexed fromTokenId,
address indexed to,
uint256 amount
);
/**
* @notice Used to retrieve the given token's specific ERC-20 balance
* @param erc20Contract The address of the ERC-20 smart contract
* @param tokenId The ID of the token being checked for ERC-20 balance
* @return The amount of the specified ERC-20 tokens owned by a given token
*/
function balanceOfERC20(
address erc20Contract,
uint256 tokenId
) external view returns (uint256);
/**
* @notice Transfer ERC-20 tokens from a specific token.
* @dev The balance MUST be transferred from this smart contract.
* @dev MUST increase the transfer-out-nonce for the tokenId
* @dev MUST revert if the `msg.sender` is not the owner of the NFT or approved to manage it.
* @param erc20Contract The address of the ERC-20 smart contract
* @param tokenId The ID of the token to transfer the ERC-20 tokens from
* @param amount The number of ERC-20 tokens to transfer
* @param data Additional data with no specified format, to allow for custom logic
*/
function transferHeldERC20FromToken(
address erc20Contract,
uint256 tokenId,
address to,
uint256 amount,
bytes memory data
) external;
/**
* @notice Transfer ERC-20 tokens to a specific token.
* @dev The ERC-20 smart contract must have approval for this contract to transfer the ERC-20 tokens.
* @dev The balance MUST be transferred from the `msg.sender`.
* @param erc20Contract The address of the ERC-20 smart contract
* @param tokenId The ID of the token to transfer ERC-20 tokens to
* @param amount The number of ERC-20 tokens to transfer
* @param data Additional data with no specified format, to allow for custom logic
*/
function transferERC20ToToken(
address erc20Contract,
uint256 tokenId,
uint256 amount,
bytes memory data
) external;
/**
* @notice Nonce increased every time an ERC20 token is transferred out of a token
* @param tokenId The ID of the token to check the nonce for
* @return The nonce of the token
*/
function erc20TransferOutNonce(
uint256 tokenId
) external view returns (uint256);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
/**
* @title IRMRKCatalog
* @author RMRK team
* @notice An interface Catalog for RMRK equippable module.
*/
interface IRMRKCatalog is IERC165 {
/**
* @notice Event to announce addition of a new part.
* @dev It is emitted when a new part is added.
* @param partId ID of the part that was added
* @param itemType Enum value specifying whether the part is `None`, `Slot` and `Fixed`
* @param zIndex An uint specifying the z value of the part. It is used to specify the depth which the part should
* be rendered at
* @param equippableAddresses An array of addresses that can equip this part
* @param metadataURI The metadata URI of the part
*/
event AddedPart(
uint64 indexed partId,
ItemType indexed itemType,
uint8 zIndex,
address[] equippableAddresses,
string metadataURI
);
/**
* @notice Event to announce new equippables to the part.
* @dev It is emitted when new addresses are marked as equippable for `partId`.
* @param partId ID of the part that had new equippable addresses added
* @param equippableAddresses An array of the new addresses that can equip this part
*/
event AddedEquippables(
uint64 indexed partId,
address[] equippableAddresses
);
/**
* @notice Event to announce the overriding of equippable addresses of the part.
* @dev It is emitted when the existing list of addresses marked as equippable for `partId` is overwritten by a new one.
* @param partId ID of the part whose list of equippable addresses was overwritten
* @param equippableAddresses The new, full, list of addresses that can equip this part
*/
event SetEquippables(uint64 indexed partId, address[] equippableAddresses);
/**
* @notice Event to announce that a given part can be equipped by any address.
* @dev It is emitted when a given part is marked as equippable by any.
* @param partId ID of the part marked as equippable by any address
*/
event SetEquippableToAll(uint64 indexed partId);
/**
* @notice Used to define a type of the item. Possible values are `None`, `Slot` or `Fixed`.
* @dev Used for fixed and slot parts.
*/
enum ItemType {
None,
Slot,
Fixed
}
/**
* @notice The integral structure of a standard RMRK catalog item defining it.
* @dev Requires a minimum of 3 storage slots per catalog item, equivalent to roughly 60,000 gas as of Berlin hard
* fork (April 14, 2021), though 5-7 storage slots is more realistic, given the standard length of an IPFS URI.
* This will result in between 25,000,000 and 35,000,000 gas per 250 assets--the maximum block size of Ethereum
* mainnet is 30M at peak usage.
* @return itemType The item type of the part
* @return z The z value of the part defining how it should be rendered when presenting the full NFT
* @return equippable The array of addresses allowed to be equipped in this part
* @return metadataURI The metadata URI of the part
*/
struct Part {
ItemType itemType; //1 byte
uint8 z; //1 byte
address[] equippable; //n Collections that can be equipped into this slot
string metadataURI; //n bytes 32+
}
/**
* @notice The structure used to add a new `Part`.
* @dev The part is added with specified ID, so you have to make sure that you are using an unused `partId`,
* otherwise the addition of the part vill be reverted.
* @dev The full `IntakeStruct` looks like this:
* [
* partID,
* [
* itemType,
* z,
* [
* permittedCollectionAddress0,
* permittedCollectionAddress1,
* permittedCollectionAddress2
* ],
* metadataURI
* ]
* ]
* @return partId ID to be assigned to the `Part`
* @return part A `Part` to be added
*/
struct IntakeStruct {
uint64 partId;
Part part;
}
/**
* @notice Used to return the metadata URI of the associated Catalog.
* @return Catalog metadata URI
*/
function getMetadataURI() external view returns (string memory);
/**
* @notice Used to return the `itemType` of the associated Catalog
* @return `itemType` of the associated Catalog
*/
function getType() external view returns (string memory);
/**
* @notice Used to check whether the given address is allowed to equip the desired `Part`.
* @dev Returns true if a collection may equip asset with `partId`.
* @param partId The ID of the part that we are checking
* @param targetAddress The address that we are checking for whether the part can be equipped into it or not
* @return isEquippable The status indicating whether the `targetAddress` can be equipped into `Part` with `partId` or not
*/
function checkIsEquippable(
uint64 partId,
address targetAddress
) external view returns (bool isEquippable);
/**
* @notice Used to check if the part is equippable by all addresses.
* @dev Returns true if part is equippable to all.
* @param partId ID of the part that we are checking
* @return isEquippableToAll The status indicating whether the part with `partId` can be equipped by any address or not
*/
function checkIsEquippableToAll(
uint64 partId
) external view returns (bool isEquippableToAll);
/**
* @notice Used to retrieve a `Part` with id `partId`
* @param partId ID of the part that we are retrieving
* @return part The `Part` struct associated with given `partId`
*/
function getPart(uint64 partId) external view returns (Part memory part);
/**
* @notice Used to retrieve multiple parts at the same time.
* @param partIds An array of part IDs that we want to retrieve
* @return part An array of `Part` structs associated with given `partIds`
*/
function getParts(
uint64[] memory partIds
) external view returns (Part[] memory part);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import "../library/RMRKErrors.sol";
/**
* @title Ownable
* @author RMRK team
* @notice A minimal ownable smart contract or owner and contributors.
* @dev This smart contract is based on "openzeppelin's access/Ownable.sol".
*/
contract Ownable is Context {
address private _owner;
mapping(address => uint256) private _contributors;
/**
* @notice Used to anounce the transfer of ownership.
* @param previousOwner Address of the account that transferred their ownership role
* @param newOwner Address of the account receiving the ownership role
*/
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
/**
* @notice Event that signifies that an address was granted contributor role or that the permission has been
* revoked.
* @dev This can only be triggered by a current owner, so there is no need to include that information in the event.
* @param contributor Address of the account that had contributor role status updated
* @param isContributor A boolean value signifying whether the role has been granted (`true`) or revoked (`false`)
*/
event ContributorUpdate(address indexed contributor, bool isContributor);
/**
* @dev Reverts if called by any account other than the owner or an approved contributor.
*/
modifier onlyOwnerOrContributor() {
_onlyOwnerOrContributor();
_;
}
/**
* @dev Reverts if called by any account other than the owner.
*/
modifier onlyOwner() {
_onlyOwner();
_;
}
/**
* @dev Initializes the contract by setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @notice Returns the address of the current owner.
* @return owner_ Address of the current owner
*/
function owner() public view virtual returns (address owner_) {
owner_ = _owner;
}
/**
* @notice Leaves the contract without owner. Functions using the `onlyOwner` modifier will be disabled.
* @dev Can only be called by the current owner.
* @dev Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is
* only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @notice Transfers ownership of the contract to a new owner.
* @dev Can only be called by the current owner.
* @param newOwner Address of the new owner's account
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) revert RMRKNewOwnerIsZeroAddress();
_transferOwnership(newOwner);
}
/**
* @notice Transfers ownership of the contract to a new owner.
* @dev Internal function without access restriction.
* @dev Emits ***OwnershipTransferred*** event.
* @param newOwner Address of the new owner's account
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @notice Adds or removes a contributor to the smart contract.
* @dev Can only be called by the owner.
* @dev Emits ***ContributorUpdate*** event.
* @param contributor Address of the contributor's account
* @param grantRole A boolean value signifying whether the contributor role is being granted (`true`) or revoked
* (`false`)
*/
function manageContributor(
address contributor,
bool grantRole
) external onlyOwner {
if (contributor == address(0)) revert RMRKNewContributorIsZeroAddress();
grantRole
? _contributors[contributor] = 1
: _contributors[contributor] = 0;
emit ContributorUpdate(contributor, grantRole);
}
/**
* @notice Used to check if the address is one of the contributors.
* @param contributor Address of the contributor whose status we are checking
* @return isContributor_ Boolean value indicating whether the address is a contributor or not
*/
function isContributor(
address contributor
) public view returns (bool isContributor_) {
isContributor_ = _contributors[contributor] == 1;
}
/**
* @notice Used to verify that the caller is either the owner or a contributor.
* @dev If the caller is not the owner or a contributor, the execution will be reverted.
*/
function _onlyOwnerOrContributor() private view {
if (owner() != _msgSender() && !isContributor(_msgSender()))
revert RMRKNotOwnerOrContributor();
}
/**
* @notice Used to verify that the caller is the owner.
* @dev If the caller is not the owner, the execution will be reverted.
*/
function _onlyOwner() private view {
if (owner() != _msgSender()) revert RMRKNotOwner();
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC2981} from "@openzeppelin/contracts/interfaces/IERC2981.sol";
import {
IERC721Metadata
} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import {
RMRKMinifiedEquippable
} from "../../RMRK/equippable/RMRKMinifiedEquippable.sol";
import {RMRKImplementationBase} from "../utils/RMRKImplementationBase.sol";
/**
* @title RMRKAbstractEquippable
* @author RMRK team
* @notice Abstract implementation of RMRK equipable module.
*/
abstract contract RMRKAbstractEquippable is
RMRKImplementationBase,
RMRKMinifiedEquippable
{
/**
* @notice Used to add an asset to a token.
* @dev If the given asset is already added to the token, the execution will be reverted.
* @dev If the asset ID is invalid, the execution will be reverted.
* @dev If the token already has the maximum amount of pending assets (128), the execution will be
* reverted.
* @param tokenId ID of the token to add the asset to
* @param assetId ID of the asset to add to the token
* @param replacesAssetWithId ID of the asset to replace from the token's list of active assets
*/
function addAssetToToken(
uint256 tokenId,
uint64 assetId,
uint64 replacesAssetWithId
) public virtual onlyOwnerOrContributor {
_addAssetToToken(tokenId, assetId, replacesAssetWithId);
}
/**
* @notice Used to add an equippable asset entry.
* @dev The ID of the asset is automatically assigned to be the next available asset ID.
* @param equippableGroupId ID of the equippable group
* @param catalogAddress Address of the `Catalog` smart contract this asset belongs to
* @param metadataURI Metadata URI of the asset
* @param partIds An array of IDs of fixed and slot parts to be included in the asset
* @return assetId The ID of the newly added asset
*/
function addEquippableAssetEntry(
uint64 equippableGroupId,
address catalogAddress,
string memory metadataURI,
uint64[] memory partIds
) public virtual onlyOwnerOrContributor returns (uint256 assetId) {
unchecked {
++_totalAssets;
}
_addAssetEntry(
uint64(_totalAssets),
equippableGroupId,
catalogAddress,
metadataURI,
partIds
);
assetId = _totalAssets;
}
/**
* @notice Used to add a asset entry.
* @dev The ID of the asset is automatically assigned to be the next available asset ID.
* @param metadataURI Metadata URI of the asset
* @return assetId ID of the newly added asset
*/
function addAssetEntry(
string memory metadataURI
) public virtual onlyOwnerOrContributor returns (uint256 assetId) {
unchecked {
++_totalAssets;
}
_addAssetEntry(uint64(_totalAssets), metadataURI);
assetId = _totalAssets;
}
/**
* @notice Used to declare that the assets belonging to a given `equippableGroupId` are equippable into the `Slot`
* associated with the `partId` of the collection at the specified `parentAddress`
* @param equippableGroupId ID of the equippable group
* @param parentAddress Address of the parent into which the equippable group can be equipped into
* @param partId ID of the `Slot` that the items belonging to the equippable group can be equipped into
*/
function setValidParentForEquippableGroup(
uint64 equippableGroupId,
address parentAddress,
uint64 partId
) public virtual onlyOwnerOrContributor {
_setValidParentForEquippableGroup(
equippableGroupId,
parentAddress,
partId
);
}
/**
* @inheritdoc IERC165
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
super.supportsInterface(interfaceId) ||
interfaceId == type(IERC2981).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
interfaceId == RMRK_INTERFACE();
}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override {
super._beforeTokenTransfer(from, to, tokenId);
if (to == address(0)) {
unchecked {
_totalSupply -= 1;
}
}
}
function _afterAddAssetToToken(
uint256 tokenId,
uint64 assetId,
uint64 replacesAssetWithId
) internal virtual override {
super._afterAddAssetToToken(tokenId, assetId, replacesAssetWithId);
// This relies on no other auto accept mechanism being in place.
// We auto accept the first ever asset or any asset added by the token owner.
// This is done to allow a meta factory to mint, add assets and accept them in one transaction.
if (
_activeAssets[tokenId].length == 0 ||
_msgSender() == ownerOf(tokenId)
) {
_acceptAsset(tokenId, _pendingAssets[tokenId].length - 1, assetId);
}
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
/**
* @title RMRKCore
* @author RMRK team
* @notice Smart contract of the RMRK core module.
* @dev This is currently just a passthrough contract which allows for granular editing of base-level ERC721 functions.
*/
contract RMRKCore {
string private constant _VERSION = "2.5.5";
bytes4 private constant _RMRK_INTERFACE = 0x524D524B; // "RMRK" in ASCII hex
/**
* @notice Version of the @rmrk-team/evm-contracts package
* @return version Version identifier for implementations of the @rmrk-team/evm-contracts package
*/
function VERSION() public pure returns (string memory version) {
version = _VERSION;
}
/**
* @notice Interface identifier of the @rmrk-team/evm-contracts package
* @return rmrkInterface Interface identifier for implementations of the @rmrk-team/evm-contracts package
*/
function RMRK_INTERFACE() public pure returns (bytes4 rmrkInterface) {
rmrkInterface = _RMRK_INTERFACE;
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
/// @title RMRKErrors
/// @author RMRK team
/// @notice A collection of errors used in the RMRK suite
/// @dev Errors are kept in a centralised file in order to provide a central point of reference and to avoid error
/// naming collisions due to inheritance
/// Attempting to grant the token to 0x0 address
error ERC721AddressZeroIsNotaValidOwner();
/// Attempting to grant approval to the current owner of the token
error ERC721ApprovalToCurrentOwner();
/// Attempting to grant approval when not being owner or approved for all should not be permitted
error ERC721ApproveCallerIsNotOwnerNorApprovedForAll();
/// Attempting to grant approval to self
error ERC721ApproveToCaller();
/// Attempting to use an invalid token ID
error ERC721InvalidTokenId();
/// Attempting to mint to 0x0 address
error ERC721MintToTheZeroAddress();
/// Attempting to manage a token without being its owner or approved by the owner
error ERC721NotApprovedOrOwner();
/// Attempting to mint an already minted token
error ERC721TokenAlreadyMinted();
/// Attempting to transfer the token from an address that is not the owner
error ERC721TransferFromIncorrectOwner();
/// Attempting to safe transfer to an address that is unable to receive the token
error ERC721TransferToNonReceiverImplementer();
/// Attempting to transfer the token to a 0x0 address
error ERC721TransferToTheZeroAddress();
/// Attempting to grant approval of assets to their current owner
error RMRKApprovalForAssetsToCurrentOwner();
/// Attempting to grant approval of assets without being the caller or approved for all
error RMRKApproveForAssetsCallerIsNotOwnerNorApprovedForAll();
/// Attempting to incorrectly configue a Catalog item
error RMRKBadConfig();
/// Attempting to set the priorities with an array of length that doesn't match the length of active assets array
error RMRKBadPriorityListLength();
/// Attempting to add an asset entry with `Part`s, without setting the `Catalog` address
error RMRKCatalogRequiredForParts();
/// Attempting to transfer a soulbound (non-transferrable) token
error RMRKCannotTransferSoulbound();
/// Attempting to accept a child that has already been accepted
error RMRKChildAlreadyExists();
/// Attempting to interact with a child, using index that is higher than the number of children
error RMRKChildIndexOutOfRange();
/// Attempting to find the index of a child token on a parent which does not own it.
error RMRKChildNotFoundInParent();
/// Attempting to equip a `Part` with a child not approved by the Catalog
error RMRKEquippableEquipNotAllowedByCatalog();
/// Attempting to use ID 0, which is not supported
/// @dev The ID 0 in RMRK suite is reserved for empty values. Guarding against its use ensures the expected operation
error RMRKIdZeroForbidden();
/// Attempting to interact with an asset, using index greater than number of assets
error RMRKIndexOutOfRange();
/// Attempting to reclaim a child that can't be reclaimed
error RMRKInvalidChildReclaim();
/// Attempting to interact with an end-user account when the contract account is expected
error RMRKIsNotContract();
/// Attempting to interact with a contract that had its operation locked
error RMRKLocked();
/// Attempting to add a pending child after the number of pending children has reached the limit (default limit is 128)
error RMRKMaxPendingChildrenReached();
/// Attempting to add a pending asset after the number of pending assets has reached the limit (default limit is
/// 128)
error RMRKMaxPendingAssetsReached();
/// Attempting to burn a total number of recursive children higher than maximum set
/// @param childContract Address of the collection smart contract in which the maximum number of recursive burns was reached
/// @param childId ID of the child token at which the maximum number of recursive burns was reached
error RMRKMaxRecursiveBurnsReached(address childContract, uint256 childId);
/// Attempting to mint a number of tokens that would cause the total supply to be greater than maximum supply
error RMRKMintOverMax();
/// Attempting to mint zero tokens
error RMRKMintZero();
/// Attempting to pass complementary arrays of different lengths
error RMRKMismachedArrayLength();
/// Attempting to transfer a child before it is unequipped
error RMRKMustUnequipFirst();
/// Attempting to nest a child over the nestable limit (current limit is 100 levels of nesting)
error RMRKNestableTooDeep();
/// Attempting to nest the token to own descendant, which would create a loop and leave the looped tokens in limbo
error RMRKNestableTransferToDescendant();
/// Attempting to nest the token to a smart contract that doesn't support nesting
error RMRKNestableTransferToNonRMRKNestableImplementer();
/// Attempting to nest the token into itself
error RMRKNestableTransferToSelf();
/// Attempting to interact with an asset that can not be found
error RMRKNoAssetMatchingId();
/// Attempting to manage an asset without owning it or having been granted permission by the owner to do so
error RMRKNotApprovedForAssetsOrOwner();
/// Attempting to interact with a token without being its owner or having been granted permission by the
/// owner to do so
/// @dev When a token is nested, only the direct owner (NFT parent) can mange it. In that case, approved addresses are
/// not allowed to manage it, in order to ensure the expected behaviour
error RMRKNotApprovedOrDirectOwner();
/// Attempting to compose an asset wihtout having an associated Catalog
error RMRKNotComposableAsset();
/// Attempting to unequip an item that isn't equipped
error RMRKNotEquipped();
/// Attempting to interact with a management function without being the smart contract's owner
error RMRKNotOwner();
/// Attempting to interact with a function without being the owner or contributor of the collection
error RMRKNotOwnerOrContributor();
/// Attempting to transfer the ownership to the 0x0 address
error RMRKNewOwnerIsZeroAddress();
/// Attempting to assign a 0x0 address as a contributor
error RMRKNewContributorIsZeroAddress();
/// Attempting an operation requiring the token being nested, while it is not
error RMRKParentIsNotNFT();
/// Attempting to add a `Part` with an ID that is already used
error RMRKPartAlreadyExists();
/// Attempting to use a `Part` that doesn't exist
error RMRKPartDoesNotExist();
/// Attempting to use a `Part` that is `Fixed` when `Slot` kind of `Part` should be used
error RMRKPartIsNotSlot();
/// Attempting to interact with a pending child using an index greater than the size of pending array
error RMRKPendingChildIndexOutOfRange();
/// Attempting to add an asset using an ID that has already been used
error RMRKAssetAlreadyExists();
/// Attempting to equip an item into a slot that already has an item equipped
error RMRKSlotAlreadyUsed();
/// Attempting to equip an item into a `Slot` that the target asset does not implement
error RMRKTargetAssetCannotReceiveSlot();
/// Attempting to equip a child into a `Slot` and parent that the child's collection doesn't support
error RMRKTokenCannotBeEquippedWithAssetIntoSlot();
/// Attempting to compose a NFT of a token without active assets
error RMRKTokenDoesNotHaveAsset();
/// Attempting to determine the asset with the top priority on a token without assets
error RMRKTokenHasNoAssets();
/// Attempting to accept or transfer a child which does not match the one at the specified index
error RMRKUnexpectedChildId();
/// Attempting to reject all pending assets but more assets than expected are pending
error RMRKUnexpectedNumberOfAssets();
/// Attempting to reject all pending children but children assets than expected are pending
error RMRKUnexpectedNumberOfChildren();
/// Attempting to accept or reject an asset which does not match the one at the specified index
error RMRKUnexpectedAssetId();
/// Attempting an operation expecting a parent to the token which is not the actual one
error RMRKUnexpectedParent();
/// Attempting not to pass an empty array of equippable addresses when adding or setting the equippable addresses
error RMRKZeroLengthIdsPassed();
/// Attempting to set the royalties to a value higher than 100% (10000 in basis points)
error RMRKRoyaltiesTooHigh();
/// Attempting to do a bulk operation on a token that is not owned by the caller
error RMRKCanOnlyDoBulkOperationsOnOwnedTokens();
/// Attempting to do a bulk operation with multiple tokens at a time
error RMRKCanOnlyDoBulkOperationsWithOneTokenAtATime();
/// Attempting to pay with native token with a value different than expected
error RMRKWrongValueSent();
// Attempting to send native token to a recipient that is unable to receive it
error TransferFailed();
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import {Ownable} from "../../RMRK/access/Ownable.sol";
import "../../RMRK/library/RMRKErrors.sol";
import {RMRKRoyalties} from "../../RMRK/extension/RMRKRoyalties.sol";
/**
* @title RMRKImplementationBase
* @author RMRK team
* @notice Smart contract of the RMRK minting utils module.
* @dev This smart contract includes the top-level utilities for managing minting and implements Ownable by default.
*/
abstract contract RMRKImplementationBase is RMRKRoyalties, Ownable {
string internal _contractURI;
string private _name;
string private _symbol;
uint256 private _nextId;
uint256 internal _totalSupply;
uint256 internal _maxSupply;
uint256 internal _totalAssets;
/**
* @notice Initializes the smart contract with a given maximum supply and minting price.
* @param name_ Name of the token collection
* @param symbol_ Symbol of the token collection
* @param contractURI_ The collection metadata URI
* @param maxSupply_ The maximum supply of tokens
* @param royaltyRecipient Address to which royalties should be sent
* @param royaltyPercentageBps The royalty percentage expressed in basis points
*/
constructor(
string memory name_,
string memory symbol_,
string memory contractURI_,
uint256 maxSupply_,
address royaltyRecipient,
uint256 royaltyPercentageBps
) RMRKRoyalties(royaltyRecipient, royaltyPercentageBps) {
_name = name_;
_symbol = symbol_;
_contractURI = contractURI_;
_maxSupply = maxSupply_;
}
/**
* @notice Used to retrieve the total supply of the tokens in a collection.
* @return totalSupply_ The number of tokens in a collection
*/
function totalSupply() public view virtual returns (uint256 totalSupply_) {
totalSupply_ = _totalSupply;
}
/**
* @notice Used to retrieve the maximum supply of the collection.
* @return maxSupply_ The maximum supply of tokens in the collection
*/
function maxSupply() public view virtual returns (uint256 maxSupply_) {
maxSupply_ = _maxSupply;
}
/**
* @notice Used to retrieve the total number of assets.
* @return totalAssets_ The total number of assets
*/
function totalAssets() public view virtual returns (uint256 totalAssets_) {
totalAssets_ = _totalAssets;
}
/**
* @notice Used to retrieve the metadata URI of the collection.
* @return contractURI_ string The metadata URI of the collection
*/
function contractURI()
public
view
virtual
returns (string memory contractURI_)
{
contractURI_ = _contractURI;
}
/**
* @notice Used to retrieve the collection name.
* @return name_ Name of the collection
*/
function name() public view virtual returns (string memory name_) {
name_ = _name;
}
/**
* @notice Used to retrieve the collection symbol.
* @return symbol_ Symbol of the collection
*/
function symbol() public view virtual returns (string memory symbol_) {
symbol_ = _symbol;
}
/**
* @inheritdoc RMRKRoyalties
*/
function updateRoyaltyRecipient(
address newRoyaltyRecipient
) public virtual override onlyOwner {
_setRoyaltyRecipient(newRoyaltyRecipient);
}
/**
* @notice Used to calculate the token IDs of tokens to be minted.
* @param numToMint Amount of tokens to be minted
* @return nextToken The ID of the first token to be minted in the current minting cycle
* @return totalSupplyOffset The ID of the last token to be minted in the current minting cycle
*/
function _prepareMint(
uint256 numToMint
) internal virtual returns (uint256 nextToken, uint256 totalSupplyOffset) {
if (numToMint == uint256(0)) revert RMRKMintZero();
if (numToMint + _nextId > _maxSupply) revert RMRKMintOverMax();
unchecked {
nextToken = _nextId + 1;
_nextId += numToMint;
_totalSupply += numToMint;
totalSupplyOffset = _nextId + 1;
}
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
/**
* @title RMRKLib
* @author RMRK team
* @notice RMRK library smart contract.
*/
library RMRKLib {
error IndexOutOfBounds();
/**
* @notice Used to remove an item from the array using the specified index.
* @dev The item is removed by replacing it with the last item and removing the last element.
* @param array An array of items containing the item to be removed
* @param index Index of the item to remove
*/
function removeItemByIndex(uint64[] storage array, uint256 index) internal {
//Check to see if this is already gated by require in all calls
if (index >= array.length) revert IndexOutOfBounds();
array[index] = array[array.length - 1];
array.pop();
}
/**
* @notice Used to determine the index of the item in the array by spedifying its value.
* @dev This was adapted from Cryptofin-Solidity `arrayUtils`.
* @dev If the item is not found the index returned will equal `0`.
* @param A The array containing the item to be found
* @param a The value of the item to find the index of
* @return The index of the item in the array
* @return A boolean value specifying whether the item was found
*/
function indexOf(
uint64[] memory A,
uint64 a
) internal pure returns (uint256, bool) {
uint256 length = A.length;
for (uint256 i; i < length; ) {
if (A[i] == a) {
return (i, true);
}
unchecked {
++i;
}
}
return (0, false);
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC5773} from "../multiasset/IERC5773.sol";
import {IERC6220} from "../equippable/IERC6220.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {
IERC721Receiver
} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {IERC7401} from "../nestable/IERC7401.sol";
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {IRMRKCatalog} from "../catalog/IRMRKCatalog.sol";
import {RMRKCore} from "../core/RMRKCore.sol";
import {RMRKLib} from "../library/RMRKLib.sol";
import {ReentrancyGuard} from "../security/ReentrancyGuard.sol";
import "../library/RMRKErrors.sol";
/**
* @title RMRKMinifiedEquippable
* @author RMRK team
* @notice Smart contract of the RMRK Equippable module, without utility internal functions.
* @dev This includes all the code for MultiAsset, Nestable and Equippable.
* @dev Most of the code is duplicated from the other legos, this version is created to save size.
*/
contract RMRKMinifiedEquippable is
ReentrancyGuard,
Context,
IERC165,
IERC721,
IERC7401,
IERC6220,
RMRKCore
{
using RMRKLib for uint64[];
uint256 private constant _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP = 100;
// Mapping owner address to token count
mapping(address => uint256) private _balances;
// Mapping from token ID to approver address to approved address
// The approver is necessary so approvals are invalidated for nested children on transfer
// WARNING: If a child NFT returns to a previous root owner, old permissions would be active again
mapping(uint256 => mapping(address => address)) private _tokenApprovals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
// ------------------- NESTABLE --------------
// Mapping from token ID to DirectOwner struct
mapping(uint256 => DirectOwner) private _RMRKOwners;
// Mapping of tokenId to array of active children structs
mapping(uint256 => Child[]) internal _activeChildren;
// Mapping of tokenId to array of pending children structs
mapping(uint256 => Child[]) internal _pendingChildren;
// Mapping of child token address to child token ID to whether they are pending or active on any token
// We might have a first extra mapping from token ID, but since the same child cannot be nested into multiple tokens
// we can strip it for size/gas savings.
mapping(address => mapping(uint256 => uint256)) internal _childIsInActive;
// -------------------------- MODIFIERS ----------------------------
/**
* @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner.
* @dev If the caller is not the owner of the token or approved to manage it by its owner, the execution will be
* reverted.
* @param tokenId ID of the token to check
*/
function _onlyApprovedOrOwner(uint256 tokenId) internal view {
address owner = ownerOf(tokenId);
if (
!(_msgSender() == owner ||
isApprovedForAll(owner, _msgSender()) ||
getApproved(tokenId) == _msgSender())
) revert ERC721NotApprovedOrOwner();
}
/**
* @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner.
* @param tokenId ID of the token to check
*/
modifier onlyApprovedOrOwner(uint256 tokenId) {
_onlyApprovedOrOwner(tokenId);
_;
}
/**
* @notice Used to verify that the caller is approved to manage the given token or it its direct owner.
* @dev This does not delegate to ownerOf, which returns the root owner, but rater uses an owner from DirectOwner
* struct.
* @dev The execution is reverted if the caller is not immediate owner or approved to manage the given token.
* @dev Used for parent-scoped transfers.
* @param tokenId ID of the token to check.
*/
function _onlyApprovedOrDirectOwner(uint256 tokenId) internal view {
(address owner, uint256 parentId, ) = directOwnerOf(tokenId);
// When the parent is an NFT, only it can do operations. Otherwise, the owner or approved address can
if (
(parentId != 0 && _msgSender() != owner) ||
!(_msgSender() == owner ||
isApprovedForAll(owner, _msgSender()) ||
getApproved(tokenId) == _msgSender())
) {
revert RMRKNotApprovedOrDirectOwner();
}
}
/**
* @notice Used to verify that the caller is approved to manage the given token or is its direct owner.
* @param tokenId ID of the token to check
*/
modifier onlyApprovedOrDirectOwner(uint256 tokenId) {
_onlyApprovedOrDirectOwner(tokenId);
_;
}
// ------------------------------- ERC721 ---------------------------------
/**
* @notice Used to retrieve the number of tokens in `owner`'s account.
* @param owner Address of the account being checked
* @return balance The balance of the given account
*/
function balanceOf(
address owner
) public view virtual returns (uint256 balance) {
if (owner == address(0)) revert ERC721AddressZeroIsNotaValidOwner();
balance = _balances[owner];
}
////////////////////////////////////////
// TRANSFERS
////////////////////////////////////////
/**
* @notice Transfers a given token from `from` to `to`.
* @dev Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* @dev Emits a {Transfer} event.
* @param from Address from which to transfer the token from
* @param to Address to which to transfer the token to
* @param tokenId ID of the token to transfer
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual onlyApprovedOrDirectOwner(tokenId) {
_transfer(from, to, tokenId, "");
}
/**
* @notice Used to safely transfer a given token token from `from` to `to`.
* @dev Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
* @dev Emits a {Transfer} event.
* @param from Address to transfer the tokens from
* @param to Address to transfer the tokens to
* @param tokenId ID of the token to transfer
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) public virtual {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @notice Used to safely transfer a given token token from `from` to `to`.
* @dev Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
* @dev Emits a {Transfer} event.
* @param from Address to transfer the tokens from
* @param to Address to transfer the tokens to
* @param tokenId ID of the token to transfer
* @param data Additional data without a specified format to be sent along with the token transaction
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory data
) public virtual onlyApprovedOrDirectOwner(tokenId) {
_transfer(from, to, tokenId, data);
if (!_checkOnERC721Received(from, to, tokenId, data))
revert ERC721TransferToNonReceiverImplementer();
}
/**
* @inheritdoc IERC7401
*/
function nestTransferFrom(
address from,
address to,
uint256 tokenId,
uint256 destinationId,
bytes memory data
) public virtual onlyApprovedOrDirectOwner(tokenId) {
(address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId);
if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner();
if (to == address(this) && tokenId == destinationId)
revert RMRKNestableTransferToSelf();
_checkDestination(to);
_checkForInheritanceLoop(tokenId, to, destinationId);
_beforeTokenTransfer(from, to, tokenId);
_beforeNestedTokenTransfer(
immediateOwner,
to,
parentId,
destinationId,
tokenId,
data
);
_balances[from] -= 1;
_updateOwnerAndClearApprovals(tokenId, destinationId, to);
_balances[to] += 1;
// Sending to NFT:
_sendToNFT(immediateOwner, to, parentId, destinationId, tokenId, data);
}
/**
* @notice Used to transfer the token from `from` to `to`.
* @dev As opposed to {transferFrom}, this imposes no restrictions on msg.sender
* @dev Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* @dev Emits a {Transfer} event.
* @param from Address of the account currently owning the given token
* @param to Address to transfer the token to
* @param tokenId ID of the token to transfer
* @param data Additional data with no specified format, sent in call to `to`
*/
function _transfer(
address from,
address to,
uint256 tokenId,
bytes memory data
) internal virtual {
(address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId);
if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner();
if (to == address(0)) revert ERC721TransferToTheZeroAddress();
_beforeTokenTransfer(from, to, tokenId);
_beforeNestedTokenTransfer(from, to, parentId, 0, tokenId, data);
_balances[from] -= 1;
_updateOwnerAndClearApprovals(tokenId, 0, to);
_balances[to] += 1;
emit Transfer(from, to, tokenId);
emit NestTransfer(from, to, parentId, 0, tokenId);
_afterTokenTransfer(from, to, tokenId);
_afterNestedTokenTransfer(from, to, parentId, 0, tokenId, data);
}
/**
* @notice Used to send a token to another token.
* @dev If the token being sent is currently owned by an externally owned account, the `parentId` should equal `0`.
* @dev Emits {Transfer} event.
* @dev Emits {NestTransfer} event.
* @param from Address from which the token is being sent
* @param to Address of the collection smart contract of the token to receive the given token
* @param parentId ID of the current parent token of the token being sent
* @param destinationId ID of the tokento receive the token being sent
* @param tokenId ID of the token being sent
* @param data Additional data with no specified format, sent in the addChild call
*/
function _sendToNFT(
address from,
address to,
uint256 parentId,
uint256 destinationId,
uint256 tokenId,
bytes memory data
) private {
IERC7401 destContract = IERC7401(to);
destContract.addChild(destinationId, tokenId, data);
emit Transfer(from, to, tokenId);
emit NestTransfer(from, to, parentId, destinationId, tokenId);
_afterTokenTransfer(from, to, tokenId);
_afterNestedTokenTransfer(
from,
to,
parentId,
destinationId,
tokenId,
data
);
}
/**
* @notice Used to check if nesting a given token into a specified token would create an inheritance loop.
* @dev If a loop would occur, the tokens would be unmanageable, so the execution is reverted if one is detected.
* @dev The check for inheritance loop is bounded to guard against too much gas being consumed.
* @param currentId ID of the token that would be nested
* @param targetContract Address of the collection smart contract of the token into which the given token would be
* nested
* @param targetId ID of the token into which the given token would be nested
*/
function _checkForInheritanceLoop(
uint256 currentId,
address targetContract,
uint256 targetId
) private view {
for (uint256 i; i < _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP; ) {
(
address nextOwner,
uint256 nextOwnerTokenId,
bool isNft
) = IERC7401(targetContract).directOwnerOf(targetId);
// If there's a final address, we're good. There's no loop.
if (!isNft) {
return;
}
// Ff the current nft is an ancestor at some point, there is an inheritance loop
if (nextOwner == address(this) && nextOwnerTokenId == currentId) {
revert RMRKNestableTransferToDescendant();
}
// We reuse the parameters to save some contract size
targetContract = nextOwner;
targetId = nextOwnerTokenId;
unchecked {
++i;
}
}
revert RMRKNestableTooDeep();
}
////////////////////////////////////////
// MINTING
////////////////////////////////////////
/**
* @notice Used to safely mint the token to the specified address while passing the additional data to contract
* recipients.
* @param to Address to which to mint the token
* @param tokenId ID of the token to mint
* @param data Additional data to send with the tokens
*/
function _safeMint(
address to,
uint256 tokenId,
bytes memory data
) internal virtual {
_innerMint(to, tokenId, 0, data);
emit Transfer(address(0), to, tokenId);
emit NestTransfer(address(0), to, 0, 0, tokenId);
_afterTokenTransfer(address(0), to, tokenId);
_afterNestedTokenTransfer(address(0), to, 0, 0, tokenId, data);
if (!_checkOnERC721Received(address(0), to, tokenId, data))
revert ERC721TransferToNonReceiverImplementer();
}
/**
* @notice Used to mint a child token to a given parent token.
* @param to Address of the collection smart contract of the token into which to mint the child token
* @param tokenId ID of the token to mint
* @param destinationId ID of the token into which to mint the new child token
* @param data Additional data with no specified format, sent in the addChild call
*/
function _nestMint(
address to,
uint256 tokenId,
uint256 destinationId,
bytes memory data
) internal virtual {
_checkDestination(to);
_innerMint(to, tokenId, destinationId, data);
_sendToNFT(address(0), to, 0, destinationId, tokenId, data);
}
/**
* @notice Used to mint a child token into a given parent token.
* @dev Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` must not exist.
* - `tokenId` must not be `0`.
* @param to Address of the collection smart contract of the token into which to mint the child token
* @param tokenId ID of the token to mint
* @param destinationId ID of the token into which to mint the new token
* @param data Additional data with no specified format, sent in call to `to`
*/
function _innerMint(
address to,
uint256 tokenId,
uint256 destinationId,
bytes memory data
) private {
if (to == address(0)) revert ERC721MintToTheZeroAddress();
if (_exists(tokenId)) revert ERC721TokenAlreadyMinted();
if (tokenId == uint256(0)) revert RMRKIdZeroForbidden();
_beforeTokenTransfer(address(0), to, tokenId);
_beforeNestedTokenTransfer(
address(0),
to,
0,
destinationId,
tokenId,
data
);
_balances[to] += 1;
_RMRKOwners[tokenId] = DirectOwner({
ownerAddress: to,
tokenId: destinationId
});
}
////////////////////////////////////////
// Ownership
////////////////////////////////////////
/**
* @inheritdoc IERC7401
*/
function ownerOf(
uint256 tokenId
) public view virtual override(IERC7401, IERC721) returns (address owner_) {
(address owner, uint256 ownerTokenId, bool isNft) = directOwnerOf(
tokenId
);
if (isNft) {
owner = IERC7401(owner).ownerOf(ownerTokenId);
}
owner_ = owner;
}
/**
* @inheritdoc IERC7401
*/
function directOwnerOf(
uint256 tokenId
)
public
view
virtual
returns (address owner_, uint256 parentId, bool isNFT)
{
DirectOwner memory owner = _RMRKOwners[tokenId];
if (owner.ownerAddress == address(0)) revert ERC721InvalidTokenId();
owner_ = owner.ownerAddress;
parentId = owner.tokenId;
isNFT = owner.tokenId != 0;
}
////////////////////////////////////////
// BURNING
////////////////////////////////////////
/**
* @notice Used to burn a given token.
* @dev In case the token has any child tokens, the execution will be reverted.
* @param tokenId ID of the token to burn
*/
function burn(uint256 tokenId) public virtual {
burn(tokenId, 0);
}
/**
* @inheritdoc IERC7401
*/
function burn(
uint256 tokenId,
uint256 maxChildrenBurns
)
public
virtual
onlyApprovedOrDirectOwner(tokenId)
returns (uint256 burnedChildren)
{
(address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId);
address rootOwner = ownerOf(tokenId);
_beforeTokenTransfer(immediateOwner, address(0), tokenId);
_beforeNestedTokenTransfer(
immediateOwner,
address(0),
parentId,
0,
tokenId,
""
);
_balances[immediateOwner] -= 1;
_approve(address(0), tokenId);
_approveForAssets(address(0), tokenId);
Child[] memory children = childrenOf(tokenId);
delete _activeChildren[tokenId];
delete _pendingChildren[tokenId];
delete _tokenApprovals[tokenId][rootOwner];
uint256 pendingRecursiveBurns;
uint256 length = children.length; //gas savings
for (uint256 i; i < length; ) {
if (burnedChildren >= maxChildrenBurns)
revert RMRKMaxRecursiveBurnsReached(
children[i].contractAddress,
children[i].tokenId
);
delete _childIsInActive[children[i].contractAddress][
children[i].tokenId
];
unchecked {
// At this point we know pendingRecursiveBurns must be at least 1
pendingRecursiveBurns = maxChildrenBurns - burnedChildren;
}
// We substract one to the next level to count for the token being burned, then add it again on returns
// This is to allow the behavior of 0 recursive burns meaning only the current token is deleted.
burnedChildren +=
IERC7401(children[i].contractAddress).burn(
children[i].tokenId,
pendingRecursiveBurns - 1
) +
1;
unchecked {
++i;
}
}
// Can't remove before burning child since child will call back to get root owner
delete _RMRKOwners[tokenId];
emit Transfer(immediateOwner, address(0), tokenId);
emit NestTransfer(immediateOwner, address(0), parentId, 0, tokenId);
_afterTokenTransfer(immediateOwner, address(0), tokenId);
_afterNestedTokenTransfer(
immediateOwner,
address(0),
parentId,
0,
tokenId,
""
);
}
////////////////////////////////////////
// APPROVALS
////////////////////////////////////////
/**
* @notice Used to grant a one-time approval to manage one's token.
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* @dev The approval is cleared when the token is transferred.
* @dev Only a single account can be approved at a time, so approving the zero address clears previous approvals.
* @dev Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
* @dev Emits an {Approval} event.
* @param to Address receiving the approval
* @param tokenId ID of the token for which the approval is being granted
*/
function approve(address to, uint256 tokenId) public virtual {
address owner = ownerOf(tokenId);
if (to == owner) revert ERC721ApprovalToCurrentOwner();
if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender()))
revert ERC721ApproveCallerIsNotOwnerNorApprovedForAll();
_approve(to, tokenId);
}
/**
* @notice Used to retrieve the account approved to manage given token.
* @dev Requirements:
*
* - `tokenId` must exist.
* @param tokenId ID of the token to check for approval
* @return approved Address of the account approved to manage the token
*/
function getApproved(
uint256 tokenId
) public view virtual returns (address approved) {
_requireMinted(tokenId);
approved = _tokenApprovals[tokenId][ownerOf(tokenId)];
}
/**
* @notice Used to approve or remove `operator` as an operator for the caller.
* @dev Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
* @dev Requirements:
*
* - The `operator` cannot be the caller.
* @dev Emits an {ApprovalForAll} event.
* @param operator Address of the operator being managed
* @param approved A boolean value signifying whether the approval is being granted (`true`) or (`revoked`)
*/
function setApprovalForAll(address operator, bool approved) public virtual {
if (_msgSender() == operator) revert ERC721ApproveToCaller();
_operatorApprovals[_msgSender()][operator] = approved;
emit ApprovalForAll(_msgSender(), operator, approved);
}
/**
* @notice Used to check if the given address is allowed to manage the tokens of the specified address.
* @param owner Address of the owner of the tokens
* @param operator Address being checked for approval
* @return isApproved A boolean value signifying whether the *operator* is allowed to manage the tokens of the *owner* (`true`)
* or not (`false`)
*/
function isApprovedForAll(
address owner,
address operator
) public view virtual returns (bool isApproved) {
isApproved = _operatorApprovals[owner][operator];
}
/**
* @notice Used to grant an approval to manage a given token.
* @dev Emits an {Approval} event.
* @param to Address to which the approval is being granted
* @param tokenId ID of the token for which the approval is being granted
*/
function _approve(address to, uint256 tokenId) internal virtual {
address owner = ownerOf(tokenId);
_tokenApprovals[tokenId][owner] = to;
emit Approval(owner, to, tokenId);
}
/**
* @notice Used to update the owner of the token and clear the approvals associated with the previous owner.
* @dev The `destinationId` should equal `0` if the new owner is an externally owned account.
* @param tokenId ID of the token being updated
* @param destinationId ID of the token to receive the given token
* @param to Address of account to receive the token
*/
function _updateOwnerAndClearApprovals(
uint256 tokenId,
uint256 destinationId,
address to
) internal {
_RMRKOwners[tokenId] = DirectOwner({
ownerAddress: to,
tokenId: destinationId
});
// Clear approvals from the previous owner
_approve(address(0), tokenId);
_approveForAssets(address(0), tokenId);
}
////////////////////////////////////////
// UTILS
////////////////////////////////////////
/**
* @notice Used to enforce that the given token has been minted.
* @dev Reverts if the `tokenId` has not been minted yet.
* @dev The validation checks whether the owner of a given token is a `0x0` address and considers it not minted if
* it is. This means that both tokens that haven't been minted yet as well as the ones that have already been
* burned will cause the transaction to be reverted.
* @param tokenId ID of the token to check
*/
function _requireMinted(uint256 tokenId) internal view virtual {
if (!_exists(tokenId)) revert ERC721InvalidTokenId();
}
/**
* @notice Used to check whether the given token exists.
* @dev Tokens start existing when they are minted (`_mint`) and stop existing when they are burned (`_burn`).
* @param tokenId ID of the token being checked
* @return exists A boolean value signifying whether the token exists
*/
function _exists(
uint256 tokenId
) internal view virtual returns (bool exists) {
exists = _RMRKOwners[tokenId].ownerAddress != address(0);
}
/**
* @notice Used to invoke {IERC721Receiver-onERC721Received} on a target address.
* @dev The call is not executed if the target address is not a contract.
* @param from Address representing the previous owner of the given token
* @param to Yarget address that will receive the tokens
* @param tokenId ID of the token to be transferred
* @param data Optional data to send along with the call
* @return valid Boolean value signifying whether the call correctly returned the expected magic value
*/
function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory data
) private returns (bool valid) {
if (to.code.length != 0) {
try
IERC721Receiver(to).onERC721Received(
_msgSender(),
from,
tokenId,
data
)
returns (bytes4 retval) {
valid = retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == uint256(0)) {
revert ERC721TransferToNonReceiverImplementer();
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
valid = true;
}
}
////////////////////////////////////////
// CHILD MANAGEMENT PUBLIC
////////////////////////////////////////
/**
* @inheritdoc IERC7401
*/
function addChild(
uint256 parentId,
uint256 childId,
bytes memory data
) public virtual {
_requireMinted(parentId);
address childAddress = _msgSender();
if (childAddress.code.length == 0) revert RMRKIsNotContract();
Child memory child = Child({
contractAddress: childAddress,
tokenId: childId
});
_beforeAddChild(parentId, childAddress, childId, data);
uint256 length = pendingChildrenOf(parentId).length;
if (length < 128) {
_pendingChildren[parentId].push(child);
} else {
revert RMRKMaxPendingChildrenReached();
}
// Previous length matches the index for the new child
emit ChildProposed(parentId, length, childAddress, childId);
_afterAddChild(parentId, childAddress, childId, data);
}
/**
* @inheritdoc IERC7401
*/
function acceptChild(
uint256 parentId,
uint256 childIndex,
address childAddress,
uint256 childId
) public virtual onlyApprovedOrOwner(parentId) {
_acceptChild(parentId, childIndex, childAddress, childId);
}
/**
* @notice Used to accept a pending child token for a given parent token.
* @dev This moves the child token from parent token's pending child tokens array into the active child tokens
* array.
* @dev Requirements:
*
* - `tokenId` must exist
* - `index` must be in range of the pending children array
* @dev Emits ***ChildAccepted*** event.
* @param parentId ID of the parent token for which the child token is being accepted
* @param childIndex Index of a child tokem in the given parent's pending children array
* @param childAddress Address of the collection smart contract of the child token expected to be located at the
* specified index of the given parent token's pending children array
* @param childId ID of the child token expected to be located at the specified index of the given parent token's
* pending children array
*/
function _acceptChild(
uint256 parentId,
uint256 childIndex,
address childAddress,
uint256 childId
) internal virtual {
Child memory child = pendingChildOf(parentId, childIndex);
_checkExpectedChild(child, childAddress, childId);
if (_childIsInActive[childAddress][childId] != 0)
revert RMRKChildAlreadyExists();
_beforeAcceptChild(parentId, childIndex, childAddress, childId);
// Remove from pending:
_removeChildByIndex(_pendingChildren[parentId], childIndex);
// Add to active:
_activeChildren[parentId].push(child);
_childIsInActive[childAddress][childId] = 1; // We use 1 as true
emit ChildAccepted(parentId, childIndex, childAddress, childId);
_afterAcceptChild(parentId, childIndex, childAddress, childId);
}
/**
* @inheritdoc IERC7401
*/
function rejectAllChildren(
uint256 tokenId,
uint256 maxRejections
) public virtual onlyApprovedOrOwner(tokenId) {
if (_pendingChildren[tokenId].length > maxRejections)
revert RMRKUnexpectedNumberOfChildren();
_beforeRejectAllChildren(tokenId);
delete _pendingChildren[tokenId];
emit AllChildrenRejected(tokenId);
_afterRejectAllChildren(tokenId);
}
/**
* @inheritdoc IERC7401
*/
function transferChild(
uint256 tokenId,
address to,
uint256 destinationId,
uint256 childIndex,
address childAddress,
uint256 childId,
bool isPending,
bytes memory data
) public virtual onlyApprovedOrOwner(tokenId) {
Child memory child;
if (isPending) {
child = pendingChildOf(tokenId, childIndex);
} else {
if (isChildEquipped(tokenId, childAddress, childId))
revert RMRKMustUnequipFirst();
child = childOf(tokenId, childIndex);
}
_checkExpectedChild(child, childAddress, childId);
_beforeTransferChild(
tokenId,
childIndex,
childAddress,
childId,
isPending,
data
);
if (isPending) {
_removeChildByIndex(_pendingChildren[tokenId], childIndex);
} else {
delete _childIsInActive[childAddress][childId];
_removeChildByIndex(_activeChildren[tokenId], childIndex);
}
if (to != address(0)) {
if (destinationId == uint256(0)) {
IERC721(childAddress).safeTransferFrom(
address(this),
to,
childId,
data
);
} else {
// Destination is an NFT
IERC7401(child.contractAddress).nestTransferFrom(
address(this),
to,
child.tokenId,
destinationId,
data
);
}
}
emit ChildTransferred(
tokenId,
childIndex,
childAddress,
childId,
isPending,
to == address(0)
);
_afterTransferChild(
tokenId,
childIndex,
childAddress,
childId,
isPending,
data
);
}
/**
* @notice Used to verify that the child being accessed is the intended child.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @param child A Child struct of a child being accessed
* @param expectedAddress The address expected to be the one of the child
* @param expectedId The token ID expected to be the one of the child
*/
function _checkExpectedChild(
Child memory child,
address expectedAddress,
uint256 expectedId
) private pure {
if (
expectedAddress != child.contractAddress ||
expectedId != child.tokenId
) revert RMRKUnexpectedChildId();
}
////////////////////////////////////////
// CHILD MANAGEMENT GETTERS
////////////////////////////////////////
/**
* @inheritdoc IERC7401
*/
function childrenOf(
uint256 parentId
) public view virtual returns (Child[] memory children) {
children = _activeChildren[parentId];
}
/**
* @inheritdoc IERC7401
*/
function pendingChildrenOf(
uint256 parentId
) public view virtual returns (Child[] memory children) {
children = _pendingChildren[parentId];
}
/**
* @inheritdoc IERC7401
*/
function childOf(
uint256 parentId,
uint256 index
) public view virtual returns (Child memory child) {
if (childrenOf(parentId).length <= index)
revert RMRKChildIndexOutOfRange();
child = _activeChildren[parentId][index];
}
/**
* @inheritdoc IERC7401
*/
function pendingChildOf(
uint256 parentId,
uint256 index
) public view virtual returns (Child memory child) {
if (pendingChildrenOf(parentId).length <= index)
revert RMRKPendingChildIndexOutOfRange();
child = _pendingChildren[parentId][index];
}
// HOOKS
/**
* @notice Hook that is called before any token transfer. This includes minting and burning.
* @dev Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be transferred to `to`.
* - When `from` is zero, `tokenId` will be minted to `to`.
* - When `to` is zero, ``from``'s `tokenId` will be burned.
* - `from` and `to` are never zero at the same time.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param from Address from which the token is being transferred
* @param to Address to which the token is being transferred
* @param tokenId ID of the token being transferred
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual {}
/**
* @notice Hook that is called after any transfer of tokens. This includes minting and burning.
* @dev Calling conditions:
*
* - When `from` and `to` are both non-zero.
* - `from` and `to` are never zero at the same time.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param from Address from which the token has been transferred
* @param to Address to which the token has been transferred
* @param tokenId ID of the token that has been transferred
*/
function _afterTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual {}
/**
* @notice Hook that is called before nested token transfer.
* @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param from Address from which the token is being transferred
* @param to Address to which the token is being transferred
* @param fromTokenId ID of the token from which the given token is being transferred
* @param toTokenId ID of the token to which the given token is being transferred
* @param tokenId ID of the token being transferred
* @param data Additional data with no specified format, sent in the addChild call
*/
function _beforeNestedTokenTransfer(
address from,
address to,
uint256 fromTokenId,
uint256 toTokenId,
uint256 tokenId,
bytes memory data
) internal virtual {}
/**
* @notice Hook that is called after nested token transfer.
* @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param from Address from which the token was transferred
* @param to Address to which the token was transferred
* @param fromTokenId ID of the token from which the given token was transferred
* @param toTokenId ID of the token to which the given token was transferred
* @param tokenId ID of the token that was transferred
* @param data Additional data with no specified format, sent in the addChild call
*/
function _afterNestedTokenTransfer(
address from,
address to,
uint256 fromTokenId,
uint256 toTokenId,
uint256 tokenId,
bytes memory data
) internal virtual {}
/**
* @notice Hook that is called before a child is added to the pending tokens array of a given token.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param tokenId ID of the token that will receive a new pending child token
* @param childAddress Address of the collection smart contract of the child token expected to be located at the
* specified index of the given parent token's pending children array
* @param childId ID of the child token expected to be located at the specified index of the given parent token's
* pending children array
* @param data Additional data with no specified format
*/
function _beforeAddChild(
uint256 tokenId,
address childAddress,
uint256 childId,
bytes memory data
) internal virtual {}
/**
* @notice Hook that is called after a child is added to the pending tokens array of a given token.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param tokenId ID of the token that has received a new pending child token
* @param childAddress Address of the collection smart contract of the child token expected to be located at the
* specified index of the given parent token's pending children array
* @param childId ID of the child token expected to be located at the specified index of the given parent token's
* pending children array
* @param data Additional data with no specified format
*/
function _afterAddChild(
uint256 tokenId,
address childAddress,
uint256 childId,
bytes memory data
) internal virtual {}
/**
* @notice Hook that is called before a child is accepted to the active tokens array of a given token.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param parentId ID of the token that will accept a pending child token
* @param childIndex Index of the child token to accept in the given parent token's pending children array
* @param childAddress Address of the collection smart contract of the child token expected to be located at the
* specified index of the given parent token's pending children array
* @param childId ID of the child token expected to be located at the specified index of the given parent token's
* pending children array
*/
function _beforeAcceptChild(
uint256 parentId,
uint256 childIndex,
address childAddress,
uint256 childId
) internal virtual {}
/**
* @notice Hook that is called after a child is accepted to the active tokens array of a given token.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param parentId ID of the token that has accepted a pending child token
* @param childIndex Index of the child token that was accpeted in the given parent token's pending children array
* @param childAddress Address of the collection smart contract of the child token that was expected to be located
* at the specified index of the given parent token's pending children array
* @param childId ID of the child token that was expected to be located at the specified index of the given parent
* token's pending children array
*/
function _afterAcceptChild(
uint256 parentId,
uint256 childIndex,
address childAddress,
uint256 childId
) internal virtual {}
/**
* @notice Hook that is called before a child is transferred from a given child token array of a given token.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param tokenId ID of the token that will transfer a child token
* @param childIndex Index of the child token that will be transferred from the given parent token's children array
* @param childAddress Address of the collection smart contract of the child token that is expected to be located
* at the specified index of the given parent token's children array
* @param childId ID of the child token that is expected to be located at the specified index of the given parent
* token's children array
* @param isPending A boolean value signifying whether the child token is being transferred from the pending child
* tokens array (`true`) or from the active child tokens array (`false`)
* @param data Additional data with no specified format, sent in the addChild call
*/
function _beforeTransferChild(
uint256 tokenId,
uint256 childIndex,
address childAddress,
uint256 childId,
bool isPending,
bytes memory data
) internal virtual {}
/**
* @notice Hook that is called after a child is transferred from a given child token array of a given token.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param tokenId ID of the token that has transferred a child token
* @param childIndex Index of the child token that was transferred from the given parent token's children array
* @param childAddress Address of the collection smart contract of the child token that was expected to be located
* at the specified index of the given parent token's children array
* @param childId ID of the child token that was expected to be located at the specified index of the given parent
* token's children array
* @param isPending A boolean value signifying whether the child token was transferred from the pending child tokens
* array (`true`) or from the active child tokens array (`false`)
* @param data Additional data with no specified format, sent in the addChild call
*/
function _afterTransferChild(
uint256 tokenId,
uint256 childIndex,
address childAddress,
uint256 childId,
bool isPending,
bytes memory data
) internal virtual {}
/**
* @notice Hook that is called before a pending child tokens array of a given token is cleared.
* @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param tokenId ID of the token that will reject all of the pending child tokens
*/
function _beforeRejectAllChildren(uint256 tokenId) internal virtual {}
/**
* @notice Hook that is called after a pending child tokens array of a given token is cleared.
* @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
* @param tokenId ID of the token that has rejected all of the pending child tokens
*/
function _afterRejectAllChildren(uint256 tokenId) internal virtual {}
// HELPERS
/**
* @notice Used to remove a specified child token form an array using its index within said array.
* @dev The caller must ensure that the length of the array is valid compared to the index passed.
* @dev The Child struct consists of the following values:
* [
* tokenId,
* contractAddress
* ]
* @param array An array od Child struct containing info about the child tokens in a given child tokens array
* @param index An index of the child token to remove in the accompanying array
*/
function _removeChildByIndex(Child[] storage array, uint256 index) private {
array[index] = array[array.length - 1];
array.pop();
}
/// Mapping of uint64 Ids to asset metadata
mapping(uint64 => string) internal _assets;
/// Mapping of tokenId to new asset, to asset to be replaced
mapping(uint256 => mapping(uint64 => uint64)) internal _assetReplacements;
/// Mapping of tokenId to an array of active assets
/// @dev Active recurses is unbounded, getting all would reach gas limit at around 30k items
/// so we leave this as internal in case a custom implementation needs to implement pagination
mapping(uint256 => uint64[]) internal _activeAssets;
/// Mapping of tokenId to an array of pending assets
mapping(uint256 => uint64[]) internal _pendingAssets;
/// Mapping of tokenId to an array of priorities for active assets
mapping(uint256 => uint64[]) internal _activeAssetPriorities;
/// Mapping of tokenId to assetId to whether the token has this asset assigned
mapping(uint256 => mapping(uint64 => bool)) internal _tokenAssets;
/// Mapping from owner to operator approvals for assets
mapping(address => mapping(address => bool))
private _operatorApprovalsForAssets;
/**
* @inheritdoc IERC5773
*/
function getAssetMetadata(
uint256 tokenId,
uint64 assetId
) public view virtual override returns (string memory metadata) {
// Allow to get the asset metadata without a token having it:
if (tokenId != 0 && !_tokenAssets[tokenId][assetId])
revert RMRKTokenDoesNotHaveAsset();
metadata = _assets[assetId];
}
/**
* @inheritdoc IERC5773
*/
function getActiveAssets(
uint256 tokenId
) public view virtual returns (uint64[] memory assetIds) {
assetIds = _activeAssets[tokenId];
}
/**
* @inheritdoc IERC5773
*/
function getPendingAssets(
uint256 tokenId
) public view virtual returns (uint64[] memory assetIds) {
assetIds = _pendingAssets[tokenId];
}
/**
* @inheritdoc IERC5773
*/
function getActiveAssetPriorities(
uint256 tokenId
) public view virtual returns (uint64[] memory priorities) {
priorities = _activeAssetPriorities[tokenId];
}
/**
* @inheritdoc IERC5773
*/
function getAssetReplacements(
uint256 tokenId,
uint64 newAssetId
) public view virtual returns (uint64 replacedAssetId) {
replacedAssetId = _assetReplacements[tokenId][newAssetId];
}
/**
* @inheritdoc IERC5773
*/
function isApprovedForAllForAssets(
address owner,
address operator
) public view virtual returns (bool isApproved) {
isApproved = _operatorApprovalsForAssets[owner][operator];
}
/**
* @inheritdoc IERC5773
*/
function setApprovalForAllForAssets(
address operator,
bool approved
) public virtual {
if (_msgSender() == operator)
revert RMRKApprovalForAssetsToCurrentOwner();
_operatorApprovalsForAssets[_msgSender()][operator] = approved;
emit ApprovalForAllForAssets(_msgSender(), operator, approved);
}
/**
* @notice Used to validate the index on the pending assets array
* @dev The call is reverted if the index is out of range or the asset Id is not present at the index.
* @param tokenId ID of the token that the asset is validated from
* @param index Index of the asset in the pending array
* @param assetId Id of the asset expected to be in the index
*/
function _validatePendingAssetAtIndex(
uint256 tokenId,
uint256 index,
uint64 assetId
) private view {
if (index >= _pendingAssets[tokenId].length)
revert RMRKIndexOutOfRange();
if (assetId != _pendingAssets[tokenId][index])
revert RMRKUnexpectedAssetId();
}
/**
* @notice Used to remove the asset at the index on the pending assets array
* @param tokenId ID of the token that the asset is being removed from
* @param index Index of the asset in the pending array
* @param assetId Id of the asset expected to be in the index
*/
function _removePendingAsset(
uint256 tokenId,
uint256 index,
uint64 assetId
) private {
_pendingAssets[tokenId].removeItemByIndex(index);
delete _assetReplacements[tokenId][assetId];
}
/**
* @notice Used to add an asset entry.
* @dev If the specified ID is already used by another asset, the execution will be reverted.
* @dev This internal function warrants custom access control to be implemented when used.
* @dev Emits ***AssetSet*** event.
* @param id ID of the asset to assign to the new asset
* @param metadataURI Metadata URI of the asset
*/
function _addAssetEntry(
uint64 id,
string memory metadataURI
) internal virtual {
if (id == uint64(0)) revert RMRKIdZeroForbidden();
if (bytes(_assets[id]).length > 0) revert RMRKAssetAlreadyExists();
_beforeAddAsset(id, metadataURI);
_assets[id] = metadataURI;
emit AssetSet(id);
_afterAddAsset(id, metadataURI);
}
/**
* @notice Used to add an asset to a token.
* @dev If the given asset is already added to the token, the execution will be reverted.
* @dev If the asset ID is invalid, the execution will be reverted.
* @dev If the token already has the maximum amount of pending assets (128), the execution will be
* reverted.
* @dev Emits ***AssetAddedToTokens*** event.
* @param tokenId ID of the token to add the asset to
* @param assetId ID of the asset to add to the token
* @param replacesAssetWithId ID of the asset to replace from the token's list of active assets
*/
function _addAssetToToken(
uint256 tokenId,
uint64 assetId,
uint64 replacesAssetWithId
) internal virtual {
if (_tokenAssets[tokenId][assetId]) revert RMRKAssetAlreadyExists();
if (bytes(_assets[assetId]).length == uint256(0))
revert RMRKNoAssetMatchingId();
if (_pendingAssets[tokenId].length >= 128)
revert RMRKMaxPendingAssetsReached();
_beforeAddAssetToToken(tokenId, assetId, replacesAssetWithId);
_tokenAssets[tokenId][assetId] = true;
_pendingAssets[tokenId].push(assetId);
if (replacesAssetWithId != uint64(0)) {
_assetReplacements[tokenId][assetId] = replacesAssetWithId;
}
uint256[] memory tokenIds = new uint256[](1);
tokenIds[0] = tokenId;
emit AssetAddedToTokens(tokenIds, assetId, replacesAssetWithId);
_afterAddAssetToToken(tokenId, assetId, replacesAssetWithId);
}
/**
* @notice Hook that is called before an asset is added.
* @param id ID of the asset
* @param metadataURI Metadata URI of the asset
*/
function _beforeAddAsset(
uint64 id,
string memory metadataURI
) internal virtual {}
/**
* @notice Hook that is called after an asset is added.
* @param id ID of the asset
* @param metadataURI Metadata URI of the asset
*/
function _afterAddAsset(
uint64 id,
string memory metadataURI
) internal virtual {}
/**
* @notice Hook that is called before adding an asset to a token's pending assets array.
* @dev If the asset doesn't intend to replace another asset, the `replacesAssetWithId` value should be `0`.
* @param tokenId ID of the token to which the asset is being added
* @param assetId ID of the asset that is being added
* @param replacesAssetWithId ID of the asset that this asset is attempting to replace
*/
function _beforeAddAssetToToken(
uint256 tokenId,
uint64 assetId,
uint64 replacesAssetWithId
) internal virtual {}
/**
* @notice Hook that is called after an asset has been added to a token's pending assets array.
* @dev If the asset doesn't intend to replace another asset, the `replacesAssetWithId` value should be `0`.
* @param tokenId ID of the token to which the asset is has been added
* @param assetId ID of the asset that is has been added
* @param replacesAssetWithId ID of the asset that this asset is attempting to replace
*/
function _afterAddAssetToToken(
uint256 tokenId,
uint64 assetId,
uint64 replacesAssetWithId
) internal virtual {}
/**
* @notice Hook that is called before an asset is accepted to a token's active assets array.
* @param tokenId ID of the token for which the asset is being accepted
* @param index Index of the asset in the token's pending assets array
* @param assetId ID of the asset expected to be located at the specified `index`
*/
function _beforeAcceptAsset(
uint256 tokenId,
uint256 index,
uint64 assetId
) internal virtual {}
/**
* @notice Hook that is called after an asset is accepted to a token's active assets array.
* @param tokenId ID of the token for which the asset has been accepted
* @param index Index of the asset in the token's pending assets array
* @param assetId ID of the asset expected to have been located at the specified `index`
* @param replacedAssetId ID of the asset that has been replaced by the accepted asset
*/
function _afterAcceptAsset(
uint256 tokenId,
uint256 index,
uint64 assetId,
uint64 replacedAssetId
) internal virtual {}
/**
* @notice Hook that is called before rejecting an asset.
* @param tokenId ID of the token from which the asset is being rejected
* @param index Index of the asset in the token's pending assets array
* @param assetId ID of the asset expected to be located at the specified `index`
*/
function _beforeRejectAsset(
uint256 tokenId,
uint256 index,
uint64 assetId
) internal virtual {}
/**
* @notice Hook that is called after rejecting an asset.
* @param tokenId ID of the token from which the asset has been rejected
* @param index Index of the asset in the token's pending assets array
* @param assetId ID of the asset expected to have been located at the specified `index`
*/
function _afterRejectAsset(
uint256 tokenId,
uint256 index,
uint64 assetId
) internal virtual {}
/**
* @notice Hook that is called before rejecting all assets of a token.
* @param tokenId ID of the token from which all of the assets are being rejected
*/
function _beforeRejectAllAssets(uint256 tokenId) internal virtual {}
/**
* @notice Hook that is called after rejecting all assets of a token.
* @param tokenId ID of the token from which all of the assets have been rejected
*/
function _afterRejectAllAssets(uint256 tokenId) internal virtual {}
/**
* @notice Hook that is called before the priorities for token's assets is set.
* @param tokenId ID of the token for which the asset priorities are being set
* @param priorities[] An array of priorities for token's active resources
*/
function _beforeSetPriority(
uint256 tokenId,
uint64[] memory priorities
) internal virtual {}
/**
* @notice Hook that is called after the priorities for token's assets is set.
* @param tokenId ID of the token for which the asset priorities have been set
* @param priorities[] An array of priorities for token's active resources
*/
function _afterSetPriority(
uint256 tokenId,
uint64[] memory priorities
) internal virtual {}
// ------------------- ASSETS --------------
// ------------------- ASSET APPROVALS --------------
/**
* @notice Mapping from token ID to approver address to approved address for assets.
* @dev The approver is necessary so approvals are invalidated for nested children on transfer.
* @dev WARNING: If a child NFT returns the original root owner, old permissions would be active again.
*/
mapping(uint256 => mapping(address => address))
private _tokenApprovalsForAssets;
// ------------------- EQUIPPABLE --------------
/// Mapping of uint64 asset ID to corresponding catalog address.
mapping(uint64 => address) internal _catalogAddresses;
/// Mapping of uint64 ID to asset object.
mapping(uint64 => uint64) internal _equippableGroupIds;
/// Mapping of assetId to catalog parts applicable to this asset, both fixed and slot
mapping(uint64 => uint64[]) internal _partIds;
/// Mapping of token ID to catalog address to slot part ID to equipment information. Used to compose an NFT.
mapping(uint256 => mapping(address => mapping(uint64 => Equipment)))
internal _equipments;
/// Mapping of token ID to child (nestable) address to child ID to count of equipped items. Used to check if equipped.
mapping(uint256 => mapping(address => mapping(uint256 => uint256)))
internal _equipCountPerChild;
/// Mapping of `equippableGroupId` to parent contract address and valid `slotId`.
mapping(uint64 => mapping(address => uint64)) internal _validParentSlots;
/**
* @notice Used to verify that the caller is either the owner of the given token or approved to manage the token's assets
* of the owner.
* @param tokenId ID of the token that we are checking
*/
function _onlyApprovedForAssetsOrOwner(uint256 tokenId) internal view {
address owner = ownerOf(tokenId);
if (
!(_msgSender() == owner ||
isApprovedForAllForAssets(owner, _msgSender()) ||
getApprovedForAssets(tokenId) == _msgSender())
) revert RMRKNotApprovedForAssetsOrOwner();
}
/**
* @notice Used to ensure that the caller is either the owner of the given token or approved to manage the token's assets
* of the owner.
* @dev If that is not the case, the execution of the function will be reverted.
* @param tokenId ID of the token that we are checking
*/
modifier onlyApprovedForAssetsOrOwner(uint256 tokenId) {
_onlyApprovedForAssetsOrOwner(tokenId);
_;
}
/**
* @inheritdoc IERC165
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
interfaceId == type(IERC165).interfaceId ||
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC7401).interfaceId ||
interfaceId == type(IERC5773).interfaceId ||
interfaceId == type(IERC6220).interfaceId;
}
// ------------------------------- ASSETS ------------------------------
// --------------------------- ASSET HANDLERS -------------------------
/**
* @notice Accepts a asset at from the pending array of given token.
* @dev Migrates the asset from the token's pending asset array to the token's active asset array.
* @dev Active assets cannot be removed by anyone, but can be replaced by a new asset.
* @dev Requirements:
*
* - The caller must own the token or be approved to manage the token's assets
* - `tokenId` must exist.
* - `index` must be in range of the length of the pending asset array.
* @dev Emits an {AssetAccepted} event.
* @param tokenId ID of the token for which to accept the pending asset
* @param index Index of the asset in the pending array to accept
* @param assetId ID of the asset that is being accepted
*/
function acceptAsset(
uint256 tokenId,
uint256 index,
uint64 assetId
) public virtual onlyApprovedForAssetsOrOwner(tokenId) {
_acceptAsset(tokenId, index, assetId);
}
/**
* @notice Used to accept a pending asset.
* @dev The call is reverted if there is no pending asset at a given index.
* @dev Emits ***AssetAccepted*** event.
* @param tokenId ID of the token for which to accept the pending asset
* @param index Index of the asset in the pending array to accept
* @param assetId ID of the asset to accept in token's pending array
*/
function _acceptAsset(
uint256 tokenId,
uint256 index,
uint64 assetId
) internal virtual {
_validatePendingAssetAtIndex(tokenId, index, assetId);
_beforeAcceptAsset(tokenId, index, assetId);
uint64 replacesId = _assetReplacements[tokenId][assetId];
uint256 replaceIndex;
bool replacefound;
if (replacesId != uint64(0))
(replaceIndex, replacefound) = _activeAssets[tokenId].indexOf(
replacesId
);
if (replacefound) {
// We don't want to remove and then push a new asset.
// This way we also keep the priority of the original asset
_activeAssets[tokenId][replaceIndex] = assetId;
delete _tokenAssets[tokenId][replacesId];
} else {
// We use the current size as next priority, by default priorities would be [0,1,2...]
_activeAssetPriorities[tokenId].push(
uint64(_activeAssets[tokenId].length)
);
_activeAssets[tokenId].push(assetId);
replacesId = uint64(0);
}
_removePendingAsset(tokenId, index, assetId);
emit AssetAccepted(tokenId, assetId, replacesId);
_afterAcceptAsset(tokenId, index, assetId, replacesId);
}
/**
* @notice Rejects a asset from the pending array of given token.
* @dev Removes the asset from the token's pending asset array.
* @dev Requirements:
*
* - The caller must own the token or be approved to manage the token's assets
* - `tokenId` must exist.
* - `index` must be in range of the length of the pending asset array.
* @dev Emits a {AssetRejected} event.
* @param tokenId ID of the token that the asset is being rejected from
* @param index Index of the asset in the pending array to be rejected
* @param assetId ID of the asset that is being rejected
*/
function rejectAsset(
uint256 tokenId,
uint256 index,
uint64 assetId
) public virtual onlyApprovedForAssetsOrOwner(tokenId) {
_validatePendingAssetAtIndex(tokenId, index, assetId);
_beforeRejectAsset(tokenId, index, assetId);
_removePendingAsset(tokenId, index, assetId);
delete _tokenAssets[tokenId][assetId];
emit AssetRejected(tokenId, assetId);
_afterRejectAsset(tokenId, index, assetId);
}
/**
* @notice Rejects all assets from the pending array of a given token.
* @dev Effecitvely deletes the pending array.
* @dev Requirements:
*
* - The caller must own the token or be approved to manage the token's assets
* - `tokenId` must exist.
* @dev Emits a {AssetRejected} event with assetId = 0.
* @param tokenId ID of the token of which to clear the pending array.
* @param maxRejections Maximum number of expected assets to reject, used to prevent from rejecting assets which
* arrive just before this operation.
*/
function rejectAllAssets(
uint256 tokenId,
uint256 maxRejections
) public virtual onlyApprovedForAssetsOrOwner(tokenId) {
uint256 len = _pendingAssets[tokenId].length;
if (len > maxRejections) revert RMRKUnexpectedNumberOfAssets();
_beforeRejectAllAssets(tokenId);
for (uint256 i; i < len; ) {
uint64 assetId = _pendingAssets[tokenId][i];
delete _assetReplacements[tokenId][assetId];
unchecked {
++i;
}
}
delete (_pendingAssets[tokenId]);
emit AssetRejected(tokenId, uint64(0));
_afterRejectAllAssets(tokenId);
}
/**
* @notice Sets a new priority array for a given token.
* @dev The priority array is a non-sequential list of `uint64`s, where the lowest value is considered highest
* priority.
* @dev Value `0` of a priority is a special case equivalent to unitialized.
* @dev Requirements:
*
* - The caller must own the token or be approved to manage the token's assets
* - `tokenId` must exist.
* - The length of `priorities` must be equal the length of the active assets array.
* @dev Emits a {AssetPrioritySet} event.
* @param tokenId ID of the token to set the priorities for
* @param priorities An array of priority values
*/
function setPriority(
uint256 tokenId,
uint64[] memory priorities
) public virtual onlyApprovedForAssetsOrOwner(tokenId) {
uint256 length = priorities.length;
if (length != _activeAssets[tokenId].length)
revert RMRKBadPriorityListLength();
_beforeSetPriority(tokenId, priorities);
_activeAssetPriorities[tokenId] = priorities;
emit AssetPrioritySet(tokenId);
_afterSetPriority(tokenId, priorities);
}
// --------------------------- ASSET INTERNALS -------------------------
/**
* @notice Used to add a asset entry.
* @dev This internal function warrants custom access control to be implemented when used.
* @param id ID of the asset being added
* @param equippableGroupId ID of the equippable group being marked as equippable into the slot associated with
* `Parts` of the `Slot` type
* @param catalogAddress Address of the `Catalog` associated with the asset
* @param metadataURI The metadata URI of the asset
* @param partIds An array of IDs of fixed and slot parts to be included in the asset
*/
function _addAssetEntry(
uint64 id,
uint64 equippableGroupId,
address catalogAddress,
string memory metadataURI,
uint64[] memory partIds
) internal virtual {
_addAssetEntry(id, metadataURI);
if (catalogAddress == address(0) && partIds.length != 0)
revert RMRKCatalogRequiredForParts();
_catalogAddresses[id] = catalogAddress;
_equippableGroupIds[id] = equippableGroupId;
_partIds[id] = partIds;
}
// ----------------------- ASSET APPROVALS ------------------------
/**
* @notice Used to grant approvals for specific tokens to a specified address.
* @dev This can only be called by the owner of the token or by an account that has been granted permission to
* manage all of the owner's assets.
* @param to Address of the account to receive the approval to the specified token
* @param tokenId ID of the token for which we are granting the permission
*/
function approveForAssets(address to, uint256 tokenId) public virtual {
address owner = ownerOf(tokenId);
if (to == owner) revert RMRKApprovalForAssetsToCurrentOwner();
if (
_msgSender() != owner &&
!isApprovedForAllForAssets(owner, _msgSender())
) revert RMRKApproveForAssetsCallerIsNotOwnerNorApprovedForAll();
_approveForAssets(to, tokenId);
}
/**
* @notice Used to get the address of the user that is approved to manage the specified token from the current
* owner.
* @param tokenId ID of the token we are checking
* @return approved Address of the account that is approved to manage the token
*/
function getApprovedForAssets(
uint256 tokenId
) public view virtual returns (address approved) {
_requireMinted(tokenId);
approved = _tokenApprovalsForAssets[tokenId][ownerOf(tokenId)];
}
/**
* @notice Internal function for granting approvals for a specific token.
* @param to Address of the account we are granting an approval to
* @param tokenId ID of the token we are granting the approval for
*/
function _approveForAssets(address to, uint256 tokenId) internal virtual {
address owner = ownerOf(tokenId);
_tokenApprovalsForAssets[tokenId][owner] = to;
emit ApprovalForAssets(owner, to, tokenId);
}
// ------------------------------- EQUIPPING ------------------------------
/**
* @inheritdoc IERC6220
*/
function equip(
IntakeEquip memory data
) public virtual onlyApprovedForAssetsOrOwner(data.tokenId) nonReentrant {
address catalogAddress = _catalogAddresses[data.assetId];
uint64 slotPartId = data.slotPartId;
if (
_equipments[data.tokenId][catalogAddress][slotPartId]
.childEquippableAddress != address(0)
) revert RMRKSlotAlreadyUsed();
// Check from parent's asset perspective:
_checkAssetAcceptsSlot(data.assetId, slotPartId);
IERC7401.Child memory child = childOf(data.tokenId, data.childIndex);
// Check from child perspective intention to be used in part
// We add reentrancy guard because of this call, it happens before updating state
if (
!IERC6220(child.contractAddress)
.canTokenBeEquippedWithAssetIntoSlot(
address(this),
child.tokenId,
data.childAssetId,
slotPartId
)
) revert RMRKTokenCannotBeEquippedWithAssetIntoSlot();
// Check from catalog perspective
if (
!IRMRKCatalog(catalogAddress).checkIsEquippable(
slotPartId,
child.contractAddress
)
) revert RMRKEquippableEquipNotAllowedByCatalog();
_beforeEquip(data);
Equipment memory newEquip = Equipment({
assetId: data.assetId,
childAssetId: data.childAssetId,
childId: child.tokenId,
childEquippableAddress: child.contractAddress
});
_equipments[data.tokenId][catalogAddress][slotPartId] = newEquip;
_equipCountPerChild[data.tokenId][child.contractAddress][
child.tokenId
] += 1;
emit ChildAssetEquipped(
data.tokenId,
data.assetId,
slotPartId,
child.tokenId,
child.contractAddress,
data.childAssetId
);
_afterEquip(data);
}
/**
* @notice Private function to check if a given asset accepts a given slot or not.
* @dev Execution will be reverted if the `Slot` does not apply for the asset.
* @param assetId ID of the asset
* @param slotPartId ID of the `Slot`
*/
function _checkAssetAcceptsSlot(
uint64 assetId,
uint64 slotPartId
) private view {
(, bool found) = _partIds[assetId].indexOf(slotPartId);
if (!found) revert RMRKTargetAssetCannotReceiveSlot();
}
/**
* @inheritdoc IERC6220
*/
function unequip(
uint256 tokenId,
uint64 assetId,
uint64 slotPartId
) public virtual onlyApprovedForAssetsOrOwner(tokenId) {
address targetCatalogAddress = _catalogAddresses[assetId];
Equipment memory equipment = _equipments[tokenId][targetCatalogAddress][
slotPartId
];
if (equipment.childEquippableAddress == address(0))
revert RMRKNotEquipped();
_beforeUnequip(tokenId, assetId, slotPartId);
delete _equipments[tokenId][targetCatalogAddress][slotPartId];
_equipCountPerChild[tokenId][equipment.childEquippableAddress][
equipment.childId
] -= 1;
emit ChildAssetUnequipped(
tokenId,
assetId,
slotPartId,
equipment.childId,
equipment.childEquippableAddress,
equipment.childAssetId
);
_afterUnequip(tokenId, assetId, slotPartId);
}
/**
* @inheritdoc IERC6220
*/
function isChildEquipped(
uint256 tokenId,
address childAddress,
uint256 childId
) public view virtual returns (bool isEquipped) {
isEquipped = _equipCountPerChild[tokenId][childAddress][childId] != 0;
}
// --------------------- ADMIN VALIDATION ---------------------
/**
* @notice Internal function used to declare that the assets belonging to a given `equippableGroupId` are
* equippable into the `Slot` associated with the `partId` of the collection at the specified `parentAddress`.
* @dev Emits ***ValidParentEquippableGroupIdSet*** event.
* @param equippableGroupId ID of the equippable group
* @param parentAddress Address of the parent into which the equippable group can be equipped into
* @param slotPartId ID of the `Slot` that the items belonging to the equippable group can be equipped into
*/
function _setValidParentForEquippableGroup(
uint64 equippableGroupId,
address parentAddress,
uint64 slotPartId
) internal virtual {
if (equippableGroupId == uint64(0) || slotPartId == uint64(0))
revert RMRKIdZeroForbidden();
_validParentSlots[equippableGroupId][parentAddress] = slotPartId;
emit ValidParentEquippableGroupIdSet(
equippableGroupId,
slotPartId,
parentAddress
);
}
/**
* @inheritdoc IERC6220
*/
function canTokenBeEquippedWithAssetIntoSlot(
address parent,
uint256 tokenId,
uint64 assetId,
uint64 slotId
) public view virtual returns (bool canBeEquipped) {
uint64 equippableGroupId = _equippableGroupIds[assetId];
uint64 equippableSlot = _validParentSlots[equippableGroupId][parent];
if (equippableSlot == slotId) {
(, bool found) = getActiveAssets(tokenId).indexOf(assetId);
canBeEquipped = found;
}
}
// --------------------- Getting Extended Assets ---------------------
/**
* @inheritdoc IERC6220
*/
function getAssetAndEquippableData(
uint256 tokenId,
uint64 assetId
)
public
view
virtual
returns (
string memory metadataURI,
uint64 equippableGroupId,
address catalogAddress,
uint64[] memory partIds
)
{
metadataURI = getAssetMetadata(tokenId, assetId);
equippableGroupId = _equippableGroupIds[assetId];
catalogAddress = _catalogAddresses[assetId];
partIds = _partIds[assetId];
}
////////////////////////////////////////
// UTILS
////////////////////////////////////////
/**
* @inheritdoc IERC6220
*/
function getEquipment(
uint256 tokenId,
address targetCatalogAddress,
uint64 slotPartId
) public view virtual returns (Equipment memory equipment) {
equipment = _equipments[tokenId][targetCatalogAddress][slotPartId];
}
/**
* @notice Checks the destination is valid for a Nest Transfer/Mint.
* @dev The destination must be a contract that implements the IERC7401 interface.
* @param to Address of the destination
*/
function _checkDestination(address to) internal view {
// Checking if it is a contract before calling it seems redundant, but otherwise it would revert with no error
if (to.code.length == 0) revert RMRKIsNotContract();
if (!IERC165(to).supportsInterface(type(IERC7401).interfaceId))
revert RMRKNestableTransferToNonRMRKNestableImplementer();
}
// HOOKS
/**
* @notice A hook to be called before a equipping a asset to the token.
* @dev The `IntakeEquip` struct consist of the following data:
* [
* tokenId,
* childIndex,
* assetId,
* slotPartId,
* childAssetId
* ]
* @param data The `IntakeEquip` struct containing data of the asset that is being equipped
*/
function _beforeEquip(IntakeEquip memory data) internal virtual {}
/**
* @notice A hook to be called after equipping a asset to the token.
* @dev The `IntakeEquip` struct consist of the following data:
* [
* tokenId,
* childIndex,
* assetId,
* slotPartId,
* childAssetId
* ]
* @param data The `IntakeEquip` struct containing data of the asset that was equipped
*/
function _afterEquip(IntakeEquip memory data) internal virtual {}
/**
* @notice A hook to be called before unequipping a asset from the token.
* @param tokenId ID of the token from which the asset is being unequipped
* @param assetId ID of the asset being unequipped
* @param slotPartId ID of the slot from which the asset is being unequipped
*/
function _beforeUnequip(
uint256 tokenId,
uint64 assetId,
uint64 slotPartId
) internal virtual {}
/**
* @notice A hook to be called after unequipping a asset from the token.
* @param tokenId ID of the token from which the asset was unequipped
* @param assetId ID of the asset that was unequipped
* @param slotPartId ID of the slot from which the asset was unequipped
*/
function _afterUnequip(
uint256 tokenId,
uint64 assetId,
uint64 slotPartId
) internal virtual {}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import {IERC2981} from "@openzeppelin/contracts/interfaces/IERC2981.sol";
import "../library/RMRKErrors.sol";
/**
* @title RMRKRoyalties
* @author RMRK team
* @notice Smart contract of the RMRK Royalties module.
*/
abstract contract RMRKRoyalties {
/** is IERC2981 **/ // Inheritance is commmnted to prevent linearization issues
address private _royaltyRecipient;
uint256 private _royaltyPercentageBps;
/**
* @notice Used to initiate the smart contract.
* @dev `royaltyPercentageBps` is expressed in basis points, so 1 basis point equals 0.01% and 500 basis points
* equal 5%.
* @param royaltyRecipient Address to which royalties should be sent
* @param royaltyPercentageBps The royalty percentage expressed in basis points
*/
constructor(address royaltyRecipient, uint256 royaltyPercentageBps) {
_setRoyaltyRecipient(royaltyRecipient);
if (royaltyPercentageBps >= 10000) revert RMRKRoyaltiesTooHigh();
_royaltyPercentageBps = royaltyPercentageBps;
}
/**
* @notice Used to update recipient of royalties.
* @dev Custom access control has to be implemented to ensure that only the intended actors can update the
* beneficiary.
* @param newRoyaltyRecipient Address of the new recipient of royalties
*/
function updateRoyaltyRecipient(
address newRoyaltyRecipient
) external virtual;
/**
* @notice Used to update the royalty recipient.
* @param newRoyaltyRecipient Address of the new recipient of royalties
*/
function _setRoyaltyRecipient(address newRoyaltyRecipient) internal {
_royaltyRecipient = newRoyaltyRecipient;
}
/**
* @notice Used to retrieve the recipient of royalties.
* @return recipient Address of the recipient of royalties
*/
function getRoyaltyRecipient()
public
view
virtual
returns (address recipient)
{
recipient = _royaltyRecipient;
}
/**
* @notice Used to retrieve the specified royalty percentage.
* @return royaltyPercentageBps The royalty percentage expressed in the basis points
*/
function getRoyaltyPercentage()
public
view
virtual
returns (uint256 royaltyPercentageBps)
{
royaltyPercentageBps = _royaltyPercentageBps;
}
/**
* @notice Used to retrieve the information about who shall receive royalties of a sale of the specified token and
* how much they will be.
* @param tokenId ID of the token for which the royalty info is being retrieved
* @param salePrice Price of the token sale
* @return receiver The beneficiary receiving royalties of the sale
* @return royaltyAmount The value of the royalties recieved by the `receiver` from the sale
*/
function royaltyInfo(
uint256 tokenId,
uint256 salePrice
) external view virtual returns (address receiver, uint256 royaltyAmount) {
uint256(tokenId); // Silence the warning about unused variable, needed for docs generation
receiver = _royaltyRecipient;
royaltyAmount = (salePrice * _royaltyPercentageBps) / 10000;
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.21;
import "./IERC7590.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
error InsufficientBalance();
error InvalidAddress();
error InvalidAmountTransferred();
error InvalidValue();
abstract contract RMRKTokenHolder is IERC7590 {
mapping(uint256 tokenId => mapping(address erc20Address => uint256 balance))
private _balances;
mapping(uint256 tokenHolderId => uint256 nonce)
private _erc20TransferOutNonce;
/**
* @inheritdoc IERC7590
*/
function balanceOfERC20(
address erc20Contract,
uint256 tokenId
) public view returns (uint256) {
return _balances[tokenId][erc20Contract];
}
/**
* @notice Transfer ERC-20 tokens from a specific token
* @dev The balance MUST be transferred from this smart contract.
* @dev Implementers should validate that the `msg.sender` is either the token owner or approved to manage it before calling this.
* @param erc20Contract The ERC-20 contract
* @param tokenId The token to transfer from
* @param amount The number of ERC-20 tokens to transfer
* @param data Additional data with no specified format, to allow for custom logic
*/
function _transferHeldERC20FromToken(
address erc20Contract,
uint256 tokenId,
address to,
uint256 amount,
bytes memory data
) internal {
if (amount == 0) {
revert InvalidValue();
}
if (to == address(0) || erc20Contract == address(0)) {
revert InvalidAddress();
}
if (_balances[tokenId][erc20Contract] < amount) {
revert InsufficientBalance();
}
_beforeTransferHeldERC20FromToken(
erc20Contract,
tokenId,
to,
amount,
data
);
IERC20 erc20 = IERC20(erc20Contract);
uint256 initBalance = erc20.balanceOf(address(this));
_balances[tokenId][erc20Contract] -= amount;
_erc20TransferOutNonce[tokenId]++;
erc20.transfer(to, amount);
uint256 newBalance = erc20.balanceOf(address(this));
// Since a token transfer could be taxed (i.e. part of the value is taken by a 3rd party), we need to check the actual amount transferred. It is up to the implementer to either revert if the difference differs from the `amount` parameter or use the actual amount transferred instead of the `amount` parameter. We revert.
if (newBalance + amount != initBalance) {
revert InvalidAmountTransferred();
}
emit TransferredERC20(erc20Contract, tokenId, to, amount);
_afterTransferHeldERC20FromToken(
erc20Contract,
tokenId,
to,
amount,
data
);
}
/**
* @inheritdoc IERC7590
*/
function transferERC20ToToken(
address erc20Contract,
uint256 tokenId,
uint256 amount,
bytes memory data
) public {
if (amount == 0) {
revert InvalidValue();
}
if (erc20Contract == address(0)) {
revert InvalidAddress();
}
_beforeTransferERC20ToToken(
erc20Contract,
tokenId,
msg.sender,
amount,
data
);
IERC20 erc20 = IERC20(erc20Contract);
uint256 initBalance = erc20.balanceOf(address(this));
erc20.transferFrom(msg.sender, address(this), amount);
uint256 newBalance = erc20.balanceOf(address(this));
// Since a token transfer could be taxed (i.e. part of the value is taken by a 3rd party), we need to check the actual amount transferred. It is up to the implementer to either revert if the difference differs from the `amount` parameter or use the actual amount transferred instead of the `amount` parameter. We revert.
if (initBalance + amount != newBalance) {
revert InvalidAmountTransferred();
}
_balances[tokenId][erc20Contract] += amount;
emit ReceivedERC20(erc20Contract, tokenId, msg.sender, amount);
_afterTransferERC20ToToken(
erc20Contract,
tokenId,
msg.sender,
amount,
data
);
}
/**
* @inheritdoc IERC7590
*/
function erc20TransferOutNonce(
uint256 tokenId
) public view returns (uint256) {
return _erc20TransferOutNonce[tokenId];
}
/**
* @notice Hook that is called before any transfer of ERC-20 tokens from a token
* @param erc20Contract The ERC-20 contract
* @param tokenId The token to transfer from
* @param to The address to send the ERC-20 tokens to
* @param amount The number of ERC-20 tokens to transfer
* @param data Additional data with no specified format, to allow for custom logic
*/
function _beforeTransferHeldERC20FromToken(
address erc20Contract,
uint256 tokenId,
address to,
uint256 amount,
bytes memory data
) internal virtual {}
/**
* @notice Hook that is called after any transfer of ERC-20 tokens from a token
* @param erc20Contract The ERC-20 contract
* @param tokenId The token to transfer from
* @param to The address to send the ERC-20 tokens to
* @param amount The number of ERC-20 tokens to transfer
* @param data Additional data with no specified format, to allow for custom logic
*/
function _afterTransferHeldERC20FromToken(
address erc20Contract,
uint256 tokenId,
address to,
uint256 amount,
bytes memory data
) internal virtual {}
/**
* @notice Hook that is called before any transfer of ERC-20 tokens to a token
* @param erc20Contract The ERC-20 contract
* @param tokenId The token to transfer from
* @param from The address to send the ERC-20 tokens from
* @param amount The number of ERC-20 tokens to transfer
* @param data Additional data with no specified format, to allow for custom logic
*/
function _beforeTransferERC20ToToken(
address erc20Contract,
uint256 tokenId,
address from,
uint256 amount,
bytes memory data
) internal virtual {}
/**
* @notice Hook that is called after any transfer of ERC-20 tokens to a token
* @param erc20Contract The ERC-20 contract
* @param tokenId The token to transfer from
* @param from The address to send the ERC-20 tokens from
* @param amount The number of ERC-20 tokens to transfer
* @param data Additional data with no specified format, to allow for custom logic
*/
function _afterTransferERC20ToToken(
address erc20Contract,
uint256 tokenId,
address from,
uint256 amount,
bytes memory data
) internal virtual {}
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return type(IERC7590).interfaceId == interfaceId;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
pragma solidity ^0.8.21;
error RentrantCall();
/**
* @title ReentrancyGuard
* @notice Smart contract used to guard against potential reentrancy exploits.
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
/**
* @notice Initializes the ReentrancyGuard with the `_status` of `_NOT_ENTERED`.
*/
constructor() {
_status = _NOT_ENTERED;
}
/**
* @notice Used to ensure that the function it is applied to cannot be reentered.
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantIn();
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @notice Used to ensure that the current call is not a reentrant call.
* @dev If reentrant call is detected, the execution will be reverted.
*/
function _nonReentrantIn() private {
// On the first call to nonReentrant, _notEntered will be true
if (_status == _ENTERED) revert RentrantCall();
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
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_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
}
{
"compilationTarget": {
"contracts/chunkies/Chunkies.sol": "Chunkies"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 10
},
"remappings": []
}
[{"inputs":[{"internalType":"string","name":"collectionMetadata","type":"string"},{"internalType":"address","name":"royaltyRecipient","type":"address"},{"internalType":"uint16","name":"royaltyPercentageBps","type":"uint16"},{"internalType":"string","name":"othalaDescription","type":"string"},{"internalType":"string","name":"regularDescription","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ERC721AddressZeroIsNotaValidOwner","type":"error"},{"inputs":[],"name":"ERC721ApprovalToCurrentOwner","type":"error"},{"inputs":[],"name":"ERC721ApproveCallerIsNotOwnerNorApprovedForAll","type":"error"},{"inputs":[],"name":"ERC721ApproveToCaller","type":"error"},{"inputs":[],"name":"ERC721InvalidTokenId","type":"error"},{"inputs":[],"name":"ERC721MintToTheZeroAddress","type":"error"},{"inputs":[],"name":"ERC721NotApprovedOrOwner","type":"error"},{"inputs":[],"name":"ERC721TokenAlreadyMinted","type":"error"},{"inputs":[],"name":"ERC721TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"ERC721TransferToNonReceiverImplementer","type":"error"},{"inputs":[],"name":"ERC721TransferToTheZeroAddress","type":"error"},{"inputs":[],"name":"IndexOutOfBounds","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InvalidAddress","type":"error"},{"inputs":[],"name":"InvalidAmountTransferred","type":"error"},{"inputs":[],"name":"InvalidValue","type":"error"},{"inputs":[],"name":"OnlyNFTOwnerCanTransferTokensFromIt","type":"error"},{"inputs":[],"name":"RMRKApprovalForAssetsToCurrentOwner","type":"error"},{"inputs":[],"name":"RMRKApproveForAssetsCallerIsNotOwnerNorApprovedForAll","type":"error"},{"inputs":[],"name":"RMRKAssetAlreadyExists","type":"error"},{"inputs":[],"name":"RMRKBadPriorityListLength","type":"error"},{"inputs":[],"name":"RMRKCatalogRequiredForParts","type":"error"},{"inputs":[],"name":"RMRKChildAlreadyExists","type":"error"},{"inputs":[],"name":"RMRKChildIndexOutOfRange","type":"error"},{"inputs":[],"name":"RMRKEquippableEquipNotAllowedByCatalog","type":"error"},{"inputs":[],"name":"RMRKIdZeroForbidden","type":"error"},{"inputs":[],"name":"RMRKIndexOutOfRange","type":"error"},{"inputs":[],"name":"RMRKIsNotContract","type":"error"},{"inputs":[],"name":"RMRKMaxPendingAssetsReached","type":"error"},{"inputs":[],"name":"RMRKMaxPendingChildrenReached","type":"error"},{"inputs":[{"internalType":"address","name":"childContract","type":"address"},{"internalType":"uint256","name":"childId","type":"uint256"}],"name":"RMRKMaxRecursiveBurnsReached","type":"error"},{"inputs":[],"name":"RMRKMintOverMax","type":"error"},{"inputs":[],"name":"RMRKMintZero","type":"error"},{"inputs":[],"name":"RMRKMustUnequipFirst","type":"error"},{"inputs":[],"name":"RMRKNestableTooDeep","type":"error"},{"inputs":[],"name":"RMRKNestableTransferToDescendant","type":"error"},{"inputs":[],"name":"RMRKNestableTransferToNonRMRKNestableImplementer","type":"error"},{"inputs":[],"name":"RMRKNestableTransferToSelf","type":"error"},{"inputs":[],"name":"RMRKNewContributorIsZeroAddress","type":"error"},{"inputs":[],"name":"RMRKNewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"RMRKNoAssetMatchingId","type":"error"},{"inputs":[],"name":"RMRKNotApprovedForAssetsOrOwner","type":"error"},{"inputs":[],"name":"RMRKNotApprovedOrDirectOwner","type":"error"},{"inputs":[],"name":"RMRKNotEquipped","type":"error"},{"inputs":[],"name":"RMRKNotOwner","type":"error"},{"inputs":[],"name":"RMRKNotOwnerOrContributor","type":"error"},{"inputs":[],"name":"RMRKPendingChildIndexOutOfRange","type":"error"},{"inputs":[],"name":"RMRKRoyaltiesTooHigh","type":"error"},{"inputs":[],"name":"RMRKSlotAlreadyUsed","type":"error"},{"inputs":[],"name":"RMRKTargetAssetCannotReceiveSlot","type":"error"},{"inputs":[],"name":"RMRKTokenCannotBeEquippedWithAssetIntoSlot","type":"error"},{"inputs":[],"name":"RMRKTokenDoesNotHaveAsset","type":"error"},{"inputs":[],"name":"RMRKUnexpectedAssetId","type":"error"},{"inputs":[],"name":"RMRKUnexpectedChildId","type":"error"},{"inputs":[],"name":"RMRKUnexpectedNumberOfAssets","type":"error"},{"inputs":[],"name":"RMRKUnexpectedNumberOfChildren","type":"error"},{"inputs":[],"name":"RentrantCall","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"StringsInsufficientHexLength","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"AllChildrenRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAllForAssets","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ApprovalForAssets","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"uint64","name":"assetId","type":"uint64"},{"indexed":true,"internalType":"uint64","name":"replacesId","type":"uint64"}],"name":"AssetAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"},{"indexed":true,"internalType":"uint64","name":"assetId","type":"uint64"},{"indexed":true,"internalType":"uint64","name":"replacesId","type":"uint64"}],"name":"AssetAddedToTokens","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"AssetPrioritySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"uint64","name":"assetId","type":"uint64"}],"name":"AssetRejected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint64","name":"assetId","type":"uint64"}],"name":"AssetSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"childIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"childAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"childId","type":"uint256"}],"name":"ChildAccepted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"uint64","name":"assetId","type":"uint64"},{"indexed":true,"internalType":"uint64","name":"slotPartId","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"childId","type":"uint256"},{"indexed":false,"internalType":"address","name":"childAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"childAssetId","type":"uint64"}],"name":"ChildAssetEquipped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"uint64","name":"assetId","type":"uint64"},{"indexed":true,"internalType":"uint64","name":"slotPartId","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"childId","type":"uint256"},{"indexed":false,"internalType":"address","name":"childAddress","type":"address"},{"indexed":false,"internalType":"uint64","name":"childAssetId","type":"uint64"}],"name":"ChildAssetUnequipped","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"childIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"childAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"childId","type":"uint256"}],"name":"ChildProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"childIndex","type":"uint256"},{"indexed":true,"internalType":"address","name":"childAddress","type":"address"},{"indexed":true,"internalType":"uint256","name":"childId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"fromPending","type":"bool"},{"indexed":false,"internalType":"bool","name":"toZero","type":"bool"}],"name":"ChildTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"contributor","type":"address"},{"indexed":false,"internalType":"bool","name":"isContributor","type":"bool"}],"name":"ContributorUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"MetadataUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"toTokenId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"NestTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"erc20Contract","type":"address"},{"indexed":true,"internalType":"uint256","name":"toTokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ReceivedERC20","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"erc20Contract","type":"address"},{"indexed":true,"internalType":"uint256","name":"fromTokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TransferredERC20","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint64","name":"equippableGroupId","type":"uint64"},{"indexed":true,"internalType":"uint64","name":"slotPartId","type":"uint64"},{"indexed":false,"internalType":"address","name":"parentAddress","type":"address"}],"name":"ValidParentEquippableGroupIdSet","type":"event"},{"inputs":[],"name":"RMRK_INTERFACE","outputs":[{"internalType":"bytes4","name":"rmrkInterface","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"version","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint64","name":"assetId","type":"uint64"}],"name":"acceptAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"parentId","type":"uint256"},{"internalType":"uint256","name":"childIndex","type":"uint256"},{"internalType":"address","name":"childAddress","type":"address"},{"internalType":"uint256","name":"childId","type":"uint256"}],"name":"acceptChild","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"metadataURI","type":"string"}],"name":"addAssetEntry","outputs":[{"internalType":"uint256","name":"assetId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint64","name":"assetId","type":"uint64"},{"internalType":"uint64","name":"replacesAssetWithId","type":"uint64"}],"name":"addAssetToToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"parentId","type":"uint256"},{"internalType":"uint256","name":"childId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"addChild","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"equippableGroupId","type":"uint64"},{"internalType":"address","name":"catalogAddress","type":"address"},{"internalType":"string","name":"metadataURI","type":"string"},{"internalType":"uint64[]","name":"partIds","type":"uint64[]"}],"name":"addEquippableAssetEntry","outputs":[{"internalType":"uint256","name":"assetId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"assetId","type":"uint64"},{"internalType":"uint64","name":"slotPartId","type":"uint64"}],"name":"addSlotPartToAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approveForAssets","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"erc20Contract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"balanceOfERC20","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"maxChildrenBurns","type":"uint256"}],"name":"burn","outputs":[{"internalType":"uint256","name":"burnedChildren","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"parent","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint64","name":"assetId","type":"uint64"},{"internalType":"uint64","name":"slotId","type":"uint64"}],"name":"canTokenBeEquippedWithAssetIntoSlot","outputs":[{"internalType":"bool","name":"canBeEquipped","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"parentId","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"childOf","outputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"contractAddress","type":"address"}],"internalType":"struct IERC7401.Child","name":"child","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"parentId","type":"uint256"}],"name":"childrenOf","outputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"contractAddress","type":"address"}],"internalType":"struct IERC7401.Child[]","name":"children","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"assetId","type":"uint64"},{"internalType":"bool","name":"isOthala_","type":"bool"},{"internalType":"bool","name":"isDead_","type":"bool"}],"name":"configureAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"contractURI","outputs":[{"internalType":"string","name":"contractURI_","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"directOwnerOf","outputs":[{"internalType":"address","name":"owner_","type":"address"},{"internalType":"uint256","name":"parentId","type":"uint256"},{"internalType":"bool","name":"isNFT","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"childIndex","type":"uint256"},{"internalType":"uint64","name":"assetId","type":"uint64"},{"internalType":"uint64","name":"slotPartId","type":"uint64"},{"internalType":"uint64","name":"childAssetId","type":"uint64"}],"internalType":"struct IERC6220.IntakeEquip","name":"data","type":"tuple"}],"name":"equip","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"erc20TransferOutNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getActiveAssetPriorities","outputs":[{"internalType":"uint64[]","name":"priorities","type":"uint64[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getActiveAssets","outputs":[{"internalType":"uint64[]","name":"assetIds","type":"uint64[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"approved","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApprovedForAssets","outputs":[{"internalType":"address","name":"approved","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint64","name":"assetId","type":"uint64"}],"name":"getAssetAndEquippableData","outputs":[{"internalType":"string","name":"metadataURI","type":"string"},{"internalType":"uint64","name":"equippableGroupId","type":"uint64"},{"internalType":"address","name":"catalogAddress","type":"address"},{"internalType":"uint64[]","name":"partIds","type":"uint64[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"assetId","type":"uint64"}],"name":"getAssetConfiguration","outputs":[{"internalType":"bool","name":"isOthala","type":"bool"},{"internalType":"bool","name":"isDead","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint64","name":"assetId","type":"uint64"}],"name":"getAssetMetadata","outputs":[{"internalType":"string","name":"metadata","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint64","name":"newAssetId","type":"uint64"}],"name":"getAssetReplacements","outputs":[{"internalType":"uint64","name":"replacedAssetId","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"targetCatalogAddress","type":"address"},{"internalType":"uint64","name":"slotPartId","type":"uint64"}],"name":"getEquipment","outputs":[{"components":[{"internalType":"uint64","name":"assetId","type":"uint64"},{"internalType":"uint64","name":"childAssetId","type":"uint64"},{"internalType":"uint256","name":"childId","type":"uint256"},{"internalType":"address","name":"childEquippableAddress","type":"address"}],"internalType":"struct IERC6220.Equipment","name":"equipment","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getPendingAssets","outputs":[{"internalType":"uint64[]","name":"assetIds","type":"uint64[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRoyaltyPercentage","outputs":[{"internalType":"uint256","name":"royaltyPercentageBps","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRoyaltyRecipient","outputs":[{"internalType":"address","name":"recipient","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"isApproved","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAllForAssets","outputs":[{"internalType":"bool","name":"isApproved","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"childAddress","type":"address"},{"internalType":"uint256","name":"childId","type":"uint256"}],"name":"isChildEquipped","outputs":[{"internalType":"bool","name":"isEquipped","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contributor","type":"address"}],"name":"isContributor","outputs":[{"internalType":"bool","name":"isContributor_","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contributor","type":"address"},{"internalType":"bool","name":"grantRole","type":"bool"}],"name":"manageContributor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maxSupply","outputs":[{"internalType":"uint256","name":"maxSupply_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"numToMint","type":"uint256"},{"internalType":"uint64","name":"assetId","type":"uint64"}],"name":"mintWithAsset","outputs":[{"internalType":"uint256","name":"firstTokenId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"name_","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"destinationId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"nestTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"owner_","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"owner_","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"parentId","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"pendingChildOf","outputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"contractAddress","type":"address"}],"internalType":"struct IERC7401.Child","name":"child","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"parentId","type":"uint256"}],"name":"pendingChildrenOf","outputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"contractAddress","type":"address"}],"internalType":"struct IERC7401.Child[]","name":"children","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"maxRejections","type":"uint256"}],"name":"rejectAllAssets","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"maxRejections","type":"uint256"}],"name":"rejectAllChildren","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint64","name":"assetId","type":"uint64"}],"name":"rejectAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint64","name":"newAssetId","type":"uint64"}],"name":"replaceMainAsset","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"salePrice","type":"uint256"}],"name":"royaltyInfo","outputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"royaltyAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAllForAssets","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collection","type":"address"},{"internalType":"bool","name":"autoAccept","type":"bool"}],"name":"setAutoAcceptCollection","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseAnimationURI","type":"string"}],"name":"setBaseAnimationURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseImageURI","type":"string"}],"name":"setBaseImageURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint64[]","name":"priorities","type":"uint64[]"}],"name":"setPriority","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"equippableGroupId","type":"uint64"},{"internalType":"address","name":"parentAddress","type":"address"},{"internalType":"uint64","name":"partId","type":"uint64"}],"name":"setValidParentForEquippableGroup","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"symbol_","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"totalAssets_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"totalSupply_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"destinationId","type":"uint256"},{"internalType":"uint256","name":"childIndex","type":"uint256"},{"internalType":"address","name":"childAddress","type":"address"},{"internalType":"uint256","name":"childId","type":"uint256"},{"internalType":"bool","name":"isPending","type":"bool"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"transferChild","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"erc20Contract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"transferERC20ToToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"erc20Contract","type":"address"},{"internalType":"uint256","name":"tokenHolderId","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"transferHeldERC20FromToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint64","name":"assetId","type":"uint64"},{"internalType":"uint64","name":"slotPartId","type":"uint64"}],"name":"unequip","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newRoyaltyRecipient","type":"address"}],"name":"updateRoyaltyRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"}]