Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 15 additions & 38 deletions contracts/extension/ContractMetadata.sol
Original file line number Diff line number Diff line change
@@ -1,48 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
pragma solidity ^0.8.19;

/// @author thirdweb
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./ContractMetadata.sol";
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file is importing itself (ContractMetadata.sol), creating a circular dependency that will cause compilation to fail.

Suggested change
import "./ContractMetadata.sol";

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update


Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contract inherits from ContractMetadata which doesn't exist in this file anymore, and the circular import will prevent compilation.

Suggested change
import "./ContractMetadata.sol";
// import "./ContractMetadata.sol"; // Removed circular import
// Minimal ContractMetadata contract definition
abstract contract ContractMetadata {
string public contractURI;
function setContractURI(string memory _uri) public virtual {
contractURI = _uri;
}
function _canSetContractURI() internal view virtual returns (bool);
}

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

interface IContractMetadata {
function contractURI() external view returns (string memory);
function setContractURI(string memory _uri) external;
event ContractURIUpdated(string prevURI, string newURI);
}

import "./interface/IContractMetadata.sol";
contract RebelByNature is ERC721URIStorage, Ownable, ContractMetadata {
Copy link

@coderabbitai coderabbitai bot Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

ContractMetadata is undefined—missing import/definition.

The contract inherits ContractMetadata, but that type is no longer declared in this file nor imported from elsewhere, so the compiler cannot resolve it. Reintroduce the original extension declaration here or import the correct contract before using it.

🤖 Prompt for AI Agents
In contracts/extension/ContractMetadata.sol around line 7, the contract inherits
ContractMetadata which is not defined or imported; add the missing declaration
or import the correct ContractMetadata contract before using it. Restore the
original ContractMetadata extension by either reintroducing its
contract/interface implementation in this file or adding an import statement
that points to the file that declares ContractMetadata, and then ensure the
inheritance list compiles (update SPDX/pragma or paths as needed).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./ContractMetadata.sol";

contract RebelByNature is ERC721URIStorage, Ownable, ContractMetadata {
uint256 public nextTokenId;

event TributeMinted(uint256 tokenId, address recipient, uint256 timestamp);

constructor() ERC721("Rebel By Nature", "RBN") {}

function mintTribute(address recipient) external onlyOwner {
    uint256 tokenId = nextTokenId++;
    _mint(recipient, tokenId);
    _setTokenURI(tokenId, contractURI);
    emit TributeMinted(tokenId, recipient, block.timestamp);
}

function _canSetContractURI() internal view override returns (bool) {
    return msg.sender == owner();
}

}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for ContractMetadata contract or interface definitions
echo "=== Searching for ContractMetadata definitions ==="
rg -nP --type=sol -C3 '^\s*(contract|interface|abstract contract)\s+ContractMetadata\b'

echo -e "\n=== Searching for IContractMetadata interface ==="
rg -nP --type=sol -C3 '^\s*interface\s+IContractMetadata\b'

echo -e "\n=== Check the interface directory ==="
fd -e sol . contracts/extension/interface/

echo -e "\n=== Git history of ContractMetadata.sol (before PR) ==="
git show main:contracts/extension/ContractMetadata.sol | head -50

Length of output: 4558


🏁 Script executed:

#!/bin/bash
# Get the IContractMetadata interface content
echo "=== IContractMetadata interface content ==="
cat contracts/extension/interface/IContractMetadata.sol

echo -e "\n=== Check if RebelByNature.sol already exists ==="
fd -e sol "RebelByNature" contracts/

Length of output: 930


@sonnyquinn24, the import import "./ContractMetadata.sol"; creates a self-import (the file importing itself), which will cause a circular dependency compilation error.

The Problem

You've completely replaced the ContractMetadata.sol file, which originally contained an abstract contract extension, with your RebelByNature contract. The file cannot import itself.

Recommended Solution: Separate Files

Restore contracts/extension/ContractMetadata.sol to its original content and create a new file for your contract:

contracts/extension/ContractMetadata.sol (restore original):

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./interface/IContractMetadata.sol";

abstract contract ContractMetadata is IContractMetadata {
    error ContractMetadataUnauthorized();

    string public override contractURI;

    function setContractURI(string memory _uri) external override {
        if (!_canSetContractURI()) {
            revert ContractMetadataUnauthorized();
        }
        _setupContractURI(_uri);
    }

    function _setupContractURI(string memory _uri) internal {
        string memory prevURI = contractURI;
        contractURI = _uri;
        emit ContractURIUpdated(prevURI, _uri);
    }

    function _canSetContractURI() internal view virtual returns (bool);
}

contracts/RebelByNature.sol (new file):

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./extension/ContractMetadata.sol";

contract RebelByNature is ERC721URIStorage, Ownable, ContractMetadata {
    uint256 public nextTokenId;

    event TributeMinted(uint256 tokenId, address recipient, uint256 timestamp);

    constructor() ERC721("Rebel By Nature", "RBN") Ownable(msg.sender) {}

    function mintTribute(address recipient) external onlyOwner {
        uint256 tokenId = nextTokenId++;
        _mint(recipient, tokenId);
        _setTokenURI(tokenId, contractURI);
        emit TributeMinted(tokenId, recipient, block.timestamp);
    }

    function _canSetContractURI() internal view override returns (bool) {
        return msg.sender == owner();
    }
}

Note: I also added Ownable(msg.sender) to the constructor, which is required for OpenZeppelin's Ownable in v5.x if you're using that version.

Alternative: Combined File (Not Recommended)

If you prefer a single file, include both contract definitions:

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./interface/IContractMetadata.sol";

abstract contract ContractMetadata is IContractMetadata {
    error ContractMetadataUnauthorized();
    string public override contractURI;

    function setContractURI(string memory _uri) external override {
        if (!_canSetContractURI()) {
            revert ContractMetadataUnauthorized();
        }
        _setupContractURI(_uri);
    }

    function _setupContractURI(string memory _uri) internal {
        string memory prevURI = contractURI;
        contractURI = _uri;
        emit ContractURIUpdated(prevURI, _uri);
    }

    function _canSetContractURI() internal view virtual returns (bool);
}

contract RebelByNature is ERC721URIStorage, Ownable, ContractMetadata {
    uint256 public nextTokenId;

    event TributeMinted(uint256 tokenId, address recipient, uint256 timestamp);

    constructor() ERC721("Rebel By Nature", "RBN") Ownable(msg.sender) {}

    function mintTribute(address recipient) external onlyOwner {
        uint256 tokenId = nextTokenId++;
        _mint(recipient, tokenId);
        _setTokenURI(tokenId, contractURI);
        emit TributeMinted(tokenId, recipient, block.timestamp);
    }

    function _canSetContractURI() internal view override returns (bool) {
        return msg.sender == owner();
    }
}

The separate files approach is strongly recommended to maintain the repository's extension architecture.

uint256 public nextTokenId;
Copy link

@coderabbitai coderabbitai bot Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Blocker: Self-import and replaced extension file will break the build and downstream imports

  • This file is contracts/extension/ContractMetadata.sol but now defines RebelByNature and also import "./ContractMetadata.sol"; (self-import). This will not compile and also removes the original extension that many modules likely import.
  • If you intend to use the thirdweb ContractMetadata extension, place RebelByNature in a new file (e.g., contracts/tokens/RebelByNature.sol) and import the actual extension from its correct path. Restore this file to the original ContractMetadata extension.

[action]

  • Move this contract to a new file and fix the import to the real ContractMetadata location.
  • Revert this file back to the extension to avoid breaking changes.

Run to locate the correct ContractMetadata definition and affected importers:


🏁 Script executed:

#!/bin/bash
# Find definitions of ContractMetadata / IContractMetadata
rg -nP --hidden -C2 '(^\s*(abstract\s+)?contract\s+ContractMetadata\b)|(^\s*interface\s+IContractMetadata\b)'

# Find all imports of ContractMetadata.sol to see impact
rg -nP --hidden -C0 'import\s+.*ContractMetadata\.sol'

# Check if this file path is referenced by others (would break consumers)
rg -nP --hidden -C2 'contracts/extension/ContractMetadata\.sol'

Length of output: 6760


Restore contracts/extension/ContractMetadata.sol and relocate RebelByNature

  • contracts/extension/ContractMetadata.sol has been overwritten by RebelByNature (with a self-import), breaking all imports of the extension.
  • Move RebelByNature into its own file (e.g., contracts/tokens/RebelByNature.sol), import the real extension from contracts/extension/upgradeable/ContractMetadata.sol, and revert contracts/extension/ContractMetadata.sol back to the original extension implementation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


/**
* @title Contract Metadata
* @notice Thirdweb's `ContractMetadata` is a contract extension for any base contracts. It lets you set a metadata URI
* for you contract.
* Additionally, `ContractMetadata` is necessary for NFT contracts that want royalties to get distributed on OpenSea.
*/
event TributeMinted(uint256 tokenId, address recipient, uint256 timestamp);

abstract contract ContractMetadata is IContractMetadata {
/// @dev The sender is not authorized to perform the action
error ContractMetadataUnauthorized();
constructor() ERC721("Rebel By Nature", "SEQREB") {}

/// @notice Returns the contract metadata URI.
string public override contractURI;

/**
* @notice Lets a contract admin set the URI for contract-level metadata.
* @dev Caller should be authorized to setup contractURI, e.g. contract admin.
* See {_canSetContractURI}.
* Emits {ContractURIUpdated Event}.
*
* @param _uri keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
*/
function setContractURI(string memory _uri) external override {
if (!_canSetContractURI()) {
revert ContractMetadataUnauthorized();
}

_setupContractURI(_uri);
function mintTribute(address recipient) external onlyOwner {
uint256 tokenId = nextTokenId++;
_mint(recipient, tokenId);
_setTokenURI(tokenId, contractURI);
emit TributeMinted(tokenId, recipient, block.timestamp);
Comment on lines +14 to +18
Copy link

@coderabbitai coderabbitai bot Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix compile error and mint safety: call contractURI(), use _safeMint

  • _setTokenURI(tokenId, contractURI); likely fails: contractURI is a function in the extension; call it as contractURI().
  • Prefer _safeMint to avoid sending tokens to non-receiver contracts.

Apply:

-    function mintTribute(address recipient) external onlyOwner {
-        uint256 tokenId = nextTokenId++;
-        _mint(recipient, tokenId);
-        _setTokenURI(tokenId, contractURI);
-        emit TributeMinted(tokenId, recipient, block.timestamp);
-    }
+    function mintTribute(address recipient) external onlyOwner {
+        uint256 tokenId = nextTokenId++;
+        _safeMint(recipient, tokenId);
+        _setTokenURI(tokenId, contractURI());
+        emit TributeMinted(tokenId, recipient, block.timestamp);
+    }

Optional: accept a per‑token URI to avoid copying the contract‑level URI into token metadata.

function mintTribute(address recipient, string calldata uri) external onlyOwner {
    uint256 tokenId = nextTokenId++;
    _safeMint(recipient, tokenId);
    _setTokenURI(tokenId, uri);
    emit TributeMinted(tokenId, recipient, block.timestamp);
}
🤖 Prompt for AI Agents
In contracts/extension/ContractMetadata.sol around lines 15 to 19, the
mintTribute implementation currently calls contractURI as if it were a variable
and uses _mint which can send tokens to non-receiver contracts; change the call
to contractURI() if you intend to use the contract-level URI, and replace _mint
with _safeMint to ensure ERC721Receiver checks. Optionally, change the function
signature to accept a per-token URI (e.g., mintTribute(address recipient, string
calldata uri)), call _safeMint(recipient, tokenId), then _setTokenURI(tokenId,
uri) (or _setTokenURI(tokenId, contractURI()) if you want the contract-level
URI), and emit the event as before.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

}

/// @dev Lets a contract admin set the URI for contract-level metadata.
function _setupContractURI(string memory _uri) internal {
string memory prevURI = contractURI;
contractURI = _uri;

emit ContractURIUpdated(prevURI, _uri);
function _canSetContractURI() internal view override returns (bool) {
return msg.sender == owner();
}
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable contractURI is not defined in this contract. Since ContractMetadata was removed, this property no longer exists.

Copilot uses AI. Check for mistakes.

/// @dev Returns whether contract metadata can be set in the given execution context.
function _canSetContractURI() internal view virtual returns (bool);
}