Colored Keys Smart Contracts
Version: 0.2.0
Solidity: 0.8.20
OpenZeppelin: 5.3.0
Overview
Colored Keys is an ERC-721 NFT system with two main features: on-chain SVG generation and a one-token-per-wallet restriction. All metadata and images are generated directly in the smart contract without external dependencies.
The system consists of two contracts:
- KeyToken.sol - Base ERC-721 with on-chain SVG generation
- OnePerWalletKeyToken.sol - Production contract that restricts ownership to one token per wallet
Quick Start
git clone https://github.com/ritovision/ritoswap.git
cd ritoswap/colored-keys
pnpm install
pnpm compile
Configure environment variables:
cp .env.example .env
# Add your PRIVATE_KEY, RECEIVER_ADDRESS, and API keys
Contract Specifications
Metric | Value | Notes |
---|---|---|
Runtime Bytecode | 18,565 bytes | 75.5% of EIP-170 limit |
Deployment Gas | 4,127,918 | Measured on testnet |
Mint Gas | ~247,000 | Including color generation and storage |
Transfer Gas | ~83,000 | Standard ERC-721 plus balance check |
Core Features
On-Chain Generation
- SVG images generated directly in contract code
- Deterministic colors based on token ID, block data, and minter address
- Complete metadata generated without external dependencies
- Base64-encoded data URIs for full compatibility
One-Token-Per-Wallet Restriction
The production contract enforces ownership limits through an _update
function override:
function _update(address to, uint256 tokenId, address auth) internal override returns (address) {
address from = _ownerOf(tokenId);
if (to != address(0) && balanceOf(to) > 0 && from != to) {
revert WouldExceedMaxTokensPerWallet(to);
}
return super._update(to, tokenId, auth);
}
This check intercepts all token movements (minting, transferring, burning) while allowing self-transfers.
Color Generation Algorithm
Colors are generated using keccak256 hashing with multiple seeds:
backgroundColor = generateColor(tokenId, block.timestamp, minter);
keyColor = generateColor(tokenId * 2, block.number, minter);
If colors are identical, the key color is inverted to ensure contrast. Note: this only detects exact matches, not visual similarity.
Deployment
Network Commands
# Local development
pnpm deploy:hardhat
pnpm deploy:local-blockchain
# Testnets
pnpm deploy:sepolia
# Mainnet (uses Viem v2, limited scripts)
pnpm deploy:mainnet
Contract addresses are automatically saved to ContractAddresses/{network}.json
in the repository root.
Mainnet deployment uses Viem v2 for enhanced reliability. Mainnet interaction is primarily intended through the dApp, so only deployment and verification scripts are provided.
Contract Verification
pnpm verify:sepolia # Requires ETHERSCAN_API_KEY
pnpm verify:mainnet # Requires ETHERSCAN_API_KEY
Contract Interaction
Basic operations using the deployed contract addresses:
# Mint one token (enforced by contract)
pnpm mint:sepolia
# Check ownership and supply
pnpm check-supply:sepolia
# Burn token (allows minting a new one)
pnpm burn:sepolia
# Transfer to RECEIVER_ADDRESS
pnpm transfer:sepolia
Testing
The contracts have comprehensive test coverage including unit tests, gas profiling, and security analysis.
# Run all tests
pnpm test
# Generate coverage report
pnpm test:coverage
# Gas consumption analysis
pnpm test:gas
# Security analysis
pnpm slither
pnpm mythril
pnpm echidna
Coverage Results
Contract | Statements | Branches | Functions | Lines |
---|---|---|---|---|
OnePerWalletKeyToken | 100% | 100% | 100% | 100% |
KeyToken | 95.83% | 100% | 88.89% | 96.15% |
SVGGenerator | 100% | 100% | 100% | 100% |
ColorGenerator | 57.89% | 16.67% | 66.67% | 62.5% |
Lower coverage in ColorGenerator is due to private utility functions tested indirectly through public interfaces.
Architecture
The contracts use standard OpenZeppelin inheritance with minimal custom logic:
ERC721 + ERC721Enumerable + ERC721Burnable
↓
KeyToken (adds color generation and SVG creation)
↓
OnePerWalletKeyToken (adds ownership restriction)
Supporting libraries:
- ColorGenerator.sol - Pure functions for color generation and hex conversion
- SVGGenerator.sol - Programmatic SVG construction
Key Functions
Public Functions
mint()
- Mint one token (checks one-per-wallet rule)mintBatch(uint256)
- Batch minting (limited to quantity=1 in production)burn(uint256)
- Burn owned tokentokenURI(uint256)
- Returns complete Base64-encoded JSON metadatagetTokenColors(uint256)
- Returns background and key colorsgetTokenOfOwner(address)
- Returns tokenId and ownership status
View Functions
balanceOf(address)
- Token count (always 0 or 1 in production)tokensOfOwner(address)
- Array of owned tokenstotalSupply()
- Current supply
Technical Details
Storage Pattern
Each token stores minimal data:
struct ColorData {
string backgroundColor; // Generated hex color
string keyColor; // Generated hex color
address minter; // Original minter
uint256 mintedAt; // Block timestamp
}
All other properties (SVG, metadata) are computed on-demand.
SVG Generation
The SVG creates a simple key design with four elements:
- Background rectangle (200x200)
- Key ring (circle at 60,100 with radius 20)
- Key shaft (rectangle from 80,95)
- Two teeth elements (positioned at x=145 and x=165)
Integration
dApp Integration
The contracts integrate with the RitoSwap dApp through standard ERC-721 interfaces plus custom view functions.
Local Blockchain Support
For local development, contracts can be deployed to the custom local blockchain:
Local Blockchain Setup GuideDocumentation
Limitations
- Color similarity detection: Only checks for identical colors, not visual similarity
- Batch minting: Limited to quantity=1 in production contract
- No upgradability: Contracts are immutable by design
- One per address: Restriction applies to addresses, not wallets (one wallet can control multiple addresses)
Development Features
The base KeyToken contract includes mintBatch(uint256)
for development testing of color generation across multiple tokens. In production, this is overridden to enforce quantity=1.
Summary
Colored Keys provides a simple ERC-721 implementation with on-chain generation and ownership restrictions. The contracts prioritize simplicity and security over complex features, using proven OpenZeppelin foundations with minimal custom logic. The one-token-per-wallet mechanism creates scarcity while the on-chain generation ensures permanent availability.