npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ndujalabs/ds-protocol

v1.2.2

Published

A set of contracts to handle dominant/subordinate NFTs

Downloads

5

Readme

'ndujaLabs Dominant-Subordinate Protocol

This package was previously called @ndujaLabs/ERC721Subordinate

A protocol to subordinate ERC721 contracts to a dominant contract so that the subordinate follows the ownership of the dominant, which can be even be an ERC721 contract that does not have any additional features.

BE CAREFUL — This is a work in progress and changes are likely to happen. Use at your own risk. Wait for this message to be removed before using in production

Why

In 2021, when we started Everdragons2, we had in mind of using the head of the dragons for a PFP token based on the Everdragons2 that you own. Here an example of a full dragon and just the head.

Dragon

DragonPFP

The question was, Should we allow people to transfer the PFP separately from the primary NFT? It didn't make much sense. At the same time, how to avoid that?

ERC721Subordinate introduces a subordinate token that are owned by whoever owns the dominant token. In consequence of this, the subordinate token cannot be approved or transferred separately from the dominant token. It is transferred when the dominant token is transferred.

The interface

// A subordinate contract has no control on its own ownership.
// Whoever owns the main token owns the subordinate token.
// ERC165 interface id is 0x431694c0
interface IERC721Subordinate {
  // The function dominantToken() returns the address of the dominant token.
  function dominantToken() external view returns (address);

  // Most marketplaces do not see tokens that have not emitted an initial
  // transfer from address 0. This function allow to fix the issue, but
  // it is not mandatory — in same cases, the deployer may want the subordinate
  // being not visible on marketplaces.
  function emitTransfer(
    address from,
    address to,
    uint256 tokenId
  ) external;
}

To avoid loops, it is paramount that the subordinate token sets the dominant token during the deployment and is not be able to change it.

The implementation

Here is the implementation of the interface in this repository (at https://github.com/ndujaLabs/erc721subordinate/blob/main/contracts/ERC721Subordinate.sol).

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

// Authors: Francesco Sullo <[email protected]>

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

import "./interfaces/IERC721Subordinate.sol";
import "./ERC721Badge.sol";

/**
 * @dev Implementation of IERC721Subordinate interface.
 * Strictly based on OpenZeppelin's implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721]
 * in openzeppelin/contracts v4.8.0.
 */
contract ERC721Subordinate is IERC721Subordinate, ERC721Badge {
  using Address for address;
  using Strings for uint256;

  error NotAnNFT();
  error TransferAlreadyEmitted();
  error OnlyDominant();

  // dominant token contract
  IERC721 private immutable _dominant;

  mapping(uint256 => bool) private _initialTransfers;

  modifier onlyDominant() {
    if (msg.sender != address(_dominant)) revert OnlyDominant();
    _;
  }

  /**
   * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection
   * plus the contract of the dominant token.
   */
  constructor(
    string memory name_,
    string memory symbol_,
    address dominant_
  ) ERC721Badge(name_, symbol_) {
    _dominant = IERC721(dominant_);
    if (!_dominant.supportsInterface(type(IERC721).interfaceId)) revert NotAnNFT();
  }

  /**
   * @dev See {IERC165-supportsInterface}.
   */
  function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721Badge) returns (bool) {
    return
      interfaceId == type(IERC721Subordinate).interfaceId ||
      interfaceId == type(IERC721).interfaceId ||
      super.supportsInterface(interfaceId);
  }

  /**
   * @dev See {IERC721Subordinate}.
   */
  function dominantToken() public view override returns (address) {
    return address(_dominant);
  }

  /**
   * @dev See {IERC721-balanceOf}.
   */
  function balanceOf(address owner) public view virtual override returns (uint256) {
    return _dominant.balanceOf(owner);
  }

  /**
   * @dev See {IERC721-ownerOf}.
   */
  function ownerOf(uint256 tokenId) public view virtual override returns (address) {
    return _dominant.ownerOf(tokenId);
  }

  function _allowTransfer(address) internal view virtual returns (bool) {
    // return _msgSender() == tokenOwner;
    return true;
  }

  function emitInitialTransfer(uint256 tokenId) external virtual {
    if (!_initialTransfers[tokenId]) revert TransferAlreadyEmitted();
    // if the token does not exist it will revert("ERC721: invalid token ID")
    address tokenOwner = _dominant.ownerOf(tokenId);
    _allowTransfer(tokenOwner);
    emit Transfer(address(0), tokenOwner, tokenId);
    _initialTransfers[tokenId] = true;
  }

  function emitTransfer(
    address from,
    address to,
    uint256 tokenId
  ) external virtual override onlyDominant {
    if (!_initialTransfers[tokenId]) {
      from = address(0);
      _initialTransfers[tokenId] = true;
    }
    emit Transfer(from, to, tokenId);
  }
}

The repo includes also an upgradeable version.

The foundation blocks

IERC721DefaultApprovable

// SPDX-License-Identifier: GPL3
pragma solidity ^0.8.17;

// Author: Francesco Sullo <[email protected]>

// erc165 interfaceId 0xbfdf8f79
interface IERC721DefaultApprovable {
  // Must be emitted when the contract is deployed.
  event DefaultApprovable(bool approvable);

  // Must be emitted any time the status changes.
  event Approvable(uint256 indexed tokenId, bool approvable);

  // Returns true if the token is approvable.
  // It should revert if the token does not exist.
  function approvable(uint256 tokenId) external view returns (bool);

  // A contract implementing this interface should not allow
  // the approval for all. So, any actor validating this interface
  // should assume that the tokens are not approvable for all.

  // An extension of this interface may include info about the
  // approval for all, but it should be considered as a separate
  // feature, not as a replacement of this interface.
}

IERC721DefaultLocked

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;

// erc165 interfaceId 0xb45a3c0e
interface IERC721DefaultLocked {
  // Must be emitted one time, when the contract is deployed,
  // defining the default status of any token that will be minted
  event DefaultLocked(bool locked);

  // Must be emitted any time the status changes
  event Locked(uint256 indexed tokenId, bool locked);

  // Returns the status of the token.
  // It should revert if the token does not exist.
  function locked(uint256 tokenId) external view returns (bool);
}

ERC721Locked

It implements the IERC721DefaultApprovable and the IERC721DefaultLocked interfaces.

Useful to build badges and soulbound tokens.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

import "./IERC721DefaultApprovable.sol";
import "./IERC721DefaultLocked.sol";

contract ERC721Badge is IERC721DefaultLocked, IERC721DefaultApprovable, ERC721{
  constructor(string memory name, string memory symbol) ERC721(name, symbol) {
    emit DefaultApprovable(false);
    emit DefaultLocked(true);
  }

  function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
    return
    interfaceId == type(IERC721DefaultApprovable).interfaceId ||
    interfaceId == type(IERC721DefaultLocked).interfaceId ||
    super.supportsInterface(interfaceId);
  }

  function approvable(uint256) external view returns (bool) {
    return false;
  }

  function locked(uint256) external view returns (bool) {
    return true;
  }

  function approve(address, uint256) public virtual override {
    revert("approvals not allowed");
  }

  function getApproved(uint256) public view virtual override returns (address) {
    return address(0);
  }

  function setApprovalForAll(address, bool) public virtual override {
    revert("approvals not allowed");
  }


  function isApprovedForAll(address, address) public view virtual override returns (bool) {
    return false;
  }

  function transferFrom(
    address,
    address,
    uint256
  ) public virtual override {
    revert("transfers not allowed");
  }

  function safeTransferFrom(
    address,
    address,
    uint256,
    bytes memory
  ) public virtual override {
    revert("transfers not allowed");
  }
}

How to use it

Install the dependencies like

npm i @openzeppelin/contracts \
 @openzeppelin/contracts-upgradeable \
 @cruna/ds-protocol

How it works

You initialize the subordinate token passing the address of the main token and the subordinate takes anything from that. Look at some example in mocks and the testing.

What makes the difference is the base token uri. Change that, and everything will work great.

A simple example:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@ndujalabs/ds-protocol/ERC721Subordinate.sol";

contract MySubordinate is ERC721Subordinate {
  constructor(address myToken) ERC721Subordinate("MyToken", "MTK", myToken) {}
}

Another example, upgradeable

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@ndujalabs/ds-protocol/ERC721SubordinateUpgradeable.sol";

contract MySubordinateUpgradeable is ERC721SubordinateUpgradeable, UUPSUpgradeable {
  /// @custom:oz-upgrades-unsafe-allow constructor
  constructor() initializer {}

  function initialize(address myTokenEnumerableUpgradeable) public initializer {
    __ERC721EnumerableSubordinate_init("SuperToken", "SPT", myTokenEnumerableUpgradeable);
  }

  function _authorizeUpgrade(address newImplementation) internal virtual override {}

  function getInterfaceId() public pure returns (bytes4) {
    return type(IERC721Subordinate).interfaceId;
  }
}

Notice that there is no reason to make the subordinate enumerable because we can query the dominant token to get all the ID owned by someone and apply that to the subordinate.

Similar proposal

There are similar proposal that moves in the same realm.

EIP-6150: Hierarchical NFTs (discussion at https://ethereum-magicians.org/t/eip-6150-hierarchical-nfts-an-extension-to-erc-721/12173) is a proposal for a new standard for non-fungible tokens (NFTs) on the Ethereum blockchain that would allow NFTs to have a hierarchical structure, similar to a filesystem. This would allow for the creation of complex structures, such as NFTs that contain other NFTs, or NFTs that represent collections of other NFTs. The proposal is currently in the discussion phase, and has not yet been implemented on the Ethereum network. ERC721Subordinate focuses instead on a simpler scenario, trying to solve a specific problem in the simplest possible way.

EIP-3652 (https://ethereum-magicians.org/t/eip-3652-hierarchical-nft/6963) is very similar to EIP-6150. Both requires all the node following the standard. ERC721Subordinate is very different because it allows to create subordinates of existing, immutable NFTs, if it is not necessary to show the subordinate on marketplaces.

Implementations

Everdragons2PFP

Feel free to make a PR to add your contracts.

History

** 1.2.2**

  • Remove NFTOwned for clarity, since it is part of the cruna-protocol

1.1.4

  • Adding missing virtual keywords in some functions

1.1.3

  • fix missing __ReentrancyGuard_init() in __ERC721Dominant_init

1.1.2

  • rename IERC721DefaultLockable to IERC721DefaultLocked

1.1.1

  • better management of initializers

1.1.0

  • adding the internal function _canAddSubordinate that must be implemented by the contract that extends the dominant, like function _canAddSubordinate() internal override onlyOwner {}

1.0.0

  • transferring the repo from ndujaLabs to cruna-cc
  • renaming from @ndujalabs/erc721subordinate to @cruna/DS-protocol

0.7.0

  • adding view function to check if an address is a subordinate
  • adding view function to check the total number of subordinates
  • (breaking change) the id of the subordinate interface is now 0x48b041fd

0.6.3

  • adding check to avoid reentrancy in the dominant token

0.6.2

  • fixing more missing virtual statement in dominant tokens

0.6.1

  • fixing missing virtual statement in dominant tokens

0.6.0

  • (breaking change) add explicit reference to subordinate in dominant, so that the dominant can propagate the emission of Transfer events to the subordinate. It is necessary to emit Transfer events in the subordinate, because offline services, like marketplaces, index Transfer events in order to list the tokens. However, it is not mandatory and a project can decide to keep its subordinates not visible in the marketplaces.

0.5.2

  • using revert error() instead of require(false, "message")

0.5.1

  • modify revert reasons in ERC721Badge for consistency

0.5.0

  • (breaking change) modify the interface to add the emitTransfer function. Any upgradeable contract implementing the previous version won't by ugradeable. While this is not ideal, it is better for future usages, considering we still are in the proposal stage.

0.4.1

  • remove the useless explicit dependency from ERC165

0.4.0

  • change the pragma of an interface that was mistakenly set to 0.8.17. Now all of them are ^0.8.9 for consistency

0.3.0

  • adding an ERC721Badge which is extended by the subordinate. BE CAREFUL, the change can break previous implementations

0.2.0

  • remove the enumerable version because it is useless in the subordinates

0.1.4version not published

  • fix error in mock, not initializing UUPSUpgradeable

0.1.3

  • remove unused dependencies (Context, ERC721Receiver)

0.1.2

  • remove script for deployment, left from the template used to create the repo

0.1.1

  • specify that _dominant is immutable, where possible

0.1.0

  • code refactored to override the implementation of OpenZeppelin's ERC721 and ERC721Enumerable to remove all the warning due to unreachable code

0.0.4

  • renamed init functions. Ex: __ERC721SubordinateUpgradeable_init >> __ERC721Subordinate_init

0.0.3

  • make it work with Solidity 0.8.17, BREAKING previous support

0.0.2

  • adding repo info to package.json

0.0.1

  • first version

Copyright

(c) 2022, Francesco Sullo [email protected]

License

MIT