Intermediate
ERC-721 NFT Implementation — Kudos build — collectibles step
Understand token IDs and metadata.
Developer Intermediate
5/6 — ERC-721 NFT Implementation — Kudos build — collectibles step
Objective Add ERC-721 badges to the Kudos app for contributors who hold Kudos tokens.
Flow Gate minting on holding Kudos, emit Transfer/Approval events, and expose a stable baseURI so marketplaces can render badges.
What to write
- Scaffold
ERC721URIStorage + AccessControl + counter.contract KudosBadge is ERC721URIStorage, AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); IERC20 public immutable kudos; uint256 private _nextId = 1; string private _base;Sets roles, links to Kudos ERC-20, and tracks token IDs.
- Constructor
Wire token address, base URI, and roles.constructor(address kudosToken, string memory base) ERC721("Kudos Badge", "KBDG") { kudos = IERC20(kudosToken); _base = base; _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(MINTER_ROLE, msg.sender); }Anchors metadata and roles; sets the ERC-721 name/symbol.
- Gate minting
Require Kudos balance and set tokenURI.function safeMint(address to, string calldata uri) external onlyRole(MINTER_ROLE) { require(kudos.balanceOf(to) > 0, "needs kudos"); uint256 id = _nextId++; _safeMint(to, id); _setTokenURI(id, uri); }Ties badges to contributors; emits standard events.
- Base URI
Expose baseURI for consistent metadata.function _baseURI() internal view override returns (string memory) { return _base; }Keeps metadata stable and indexer-friendly.
Why Gating on Kudos ties badges to real contribution, stable URIs make marketplaces happy, and role-gated minting avoids rogue supply.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC721URIStorage, ERC721} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract KudosBadge is ERC721URIStorage, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
IERC20 public immutable kudos;
uint256 private _nextId = 1;
string private _base;
constructor(address kudosToken, string memory base) ERC721("Kudos Badge", "KBDG") {
kudos = IERC20(kudosToken);
_base = base;
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}
function safeMint(address to, string calldata uri) external onlyRole(MINTER_ROLE) {
require(kudos.balanceOf(to) > 0, "needs kudos");
uint256 id = _nextId++;
_safeMint(to, id);
_setTokenURI(id, uri);
}
function _baseURI() internal view override returns (string memory) { return _base; }
function supportsInterface(bytes4 id) public view override(ERC721URIStorage, AccessControl) returns (bool) {
return super.supportsInterface(id);
}
}
Next Plug in oracles and randomness to enrich badge traits.