Version: 1.0 Last Updated: October 13, 2025 Network: Sepolia Testnet (Chain ID: 11155111)
The CopyrightRegistry contract provides blockchain-based copyright registration for Indonesian creative works. It offers:
- Tamper-proof copyright registration with immutable timestamps
- Content hash verification using SHA-256 + IPFS CID
- Multi-asset type support (Art, Music, Writing, Photography, Design)
- Duplicate detection to prevent re-registration
- Public verification for anyone to verify ownership
Status: ✅ Deployed (Local) Test Coverage: 100% (19/19 tests passing) Gas Cost: ~412,000 gas per registration
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract CopyrightRegistry {
// Implementation at: contracts/CopyrightRegistry.sol
}Compiler Version: 0.8.20 Optimization: Enabled (200 runs) License: MIT
Supported types of creative assets:
enum AssetType {
Art, // 0 - Digital art, illustrations, graphics
Music, // 1 - Music tracks, albums, audio samples
Writing, // 2 - Articles, books, scripts, poetry
Photography, // 3 - Photographs, photo collections
Design // 4 - Logos, templates, UI kits, designs
}Complete registration record:
struct Registration {
uint256 id; // Unique registration ID (auto-increment)
address creator; // Creator's wallet address
bytes32 contentHash; // SHA-256 hash of content
string ipfsCID; // IPFS Content Identifier
string title; // Work title
string description; // Work description
AssetType assetType; // Type of creative asset
string[] tags; // Searchable tags
uint256 timestamp; // Block timestamp of registration
bool exists; // Existence flag (internal use)
}Register a creative work with copyright protection.
function registerCopyright(
bytes32 contentHash,
string memory ipfsCID,
string memory title,
string memory description,
AssetType assetType,
string[] memory tags
) external returns (uint256 registrationId)Parameters:
contentHash- SHA-256 hash of the content (usekeccak256(content)for blockchain hash)ipfsCID- IPFS Content Identifier (e.g., "QmXxx...")title- Title of the creative work (required, non-empty)description- Description of the work (can be empty)assetType- Type of creative asset (0-4, see AssetType enum)tags- Array of searchable tags (can be empty)
Returns:
registrationId- Unique ID of the new registration
Requirements:
- Content hash must not be empty (non-zero bytes32)
- Title must not be empty
- IPFS CID must not be empty
- Content must not already be registered (duplicate check)
Emits:
CopyrightRegistered(uint256 id, address creator, bytes32 contentHash, string ipfsCID, AssetType assetType)
Example:
const contentHash = ethers.id("my-unique-content");
const tx = await registry.registerCopyright(
contentHash,
"QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG",
"My Digital Artwork",
"A beautiful piece created in 2025",
0, // Art
["digital", "art", "2025"]
);
const receipt = await tx.wait();
// registrationId is returned and can be extracted from eventsGet full registration details by ID.
function getRegistration(uint256 registrationId)
external view returns (Registration memory)Parameters:
registrationId- The registration ID to query
Returns:
- Full
Registrationstruct with all details
Reverts:
- "Registration does not exist" - if ID is invalid
Example:
const registration = await registry.getRegistration(1);
console.log(registration.title); // "My Digital Artwork"
console.log(registration.creator); // "0x..."Get registration details by content hash.
function getRegistrationByHash(bytes32 contentHash)
external view returns (Registration memory)Parameters:
contentHash- The content hash to look up
Returns:
- Full
Registrationstruct
Reverts:
- "Content not registered" - if hash not found
Example:
const contentHash = ethers.id("my-unique-content");
const registration = await registry.getRegistrationByHash(contentHash);Get total number of registrations.
function getTotalRegistrations() external view returns (uint256)Returns:
- Total count of all registrations
Example:
const total = await registry.getTotalRegistrations();
console.log(`Total: ${total} works registered`);Get all registration IDs by a specific creator.
function getRegistrationsByCreator(address creator)
external view returns (uint256[] memory)Parameters:
creator- Creator's wallet address
Returns:
- Array of registration IDs owned by the creator
Example:
const creatorWorks = await registry.getRegistrationsByCreator(creatorAddress);
// [1, 5, 7, 12] - IDs of all works by this creatorGet count of registrations by a creator.
function getCreatorRegistrationCount(address creator)
external view returns (uint256)Parameters:
creator- Creator's wallet address
Returns:
- Number of registrations
Example:
const count = await registry.getCreatorRegistrationCount(creatorAddress);
console.log(`Creator has ${count} registered works`);Get all registration IDs by asset type.
function getRegistrationsByAssetType(AssetType assetType)
external view returns (uint256[] memory)Parameters:
assetType- The asset type to query (0-4)
Returns:
- Array of registration IDs of that type
Example:
// Get all Art registrations
const artWorks = await registry.getRegistrationsByAssetType(0);
// [1, 3, 5, 8] - IDs of all Art assetsGet count of registrations by asset type.
function getAssetTypeCount(AssetType assetType)
external view returns (uint256)Parameters:
assetType- The asset type to query
Returns:
- Number of registrations of that type
Example:
const artCount = await registry.getAssetTypeCount(0);
console.log(`${artCount} artworks registered`);Check if content hash is already registered.
function isContentRegistered(bytes32 contentHash)
public view returns (bool)Parameters:
contentHash- Content hash to check
Returns:
trueif registered,falseotherwise
Example:
const contentHash = ethers.id("my-content");
const isRegistered = await registry.isContentRegistered(contentHash);
if (isRegistered) {
console.log("This content is already registered!");
}Get registration ID by content hash.
function getRegistrationIdByHash(bytes32 contentHash)
external view returns (uint256)Parameters:
contentHash- Content hash to look up
Returns:
- Registration ID, or
0if not found
Example:
const contentHash = ethers.id("my-content");
const id = await registry.getRegistrationIdByHash(contentHash);
// Returns 0 if not found, otherwise returns the registration IDVerify if an address is the creator of a registration.
function isCreator(uint256 registrationId, address account)
external view returns (bool)Parameters:
registrationId- Registration ID to checkaccount- Address to verify
Returns:
trueif account is the creator
Reverts:
- "Registration does not exist" - if ID is invalid
Example:
const isOwner = await registry.isCreator(1, userAddress);
if (isOwner) {
console.log("User owns this work");
}Emitted when a new copyright is registered.
event CopyrightRegistered(
uint256 indexed id,
address indexed creator,
bytes32 indexed contentHash,
string ipfsCID,
AssetType assetType
);Parameters:
id- Unique registration ID (indexed for filtering)creator- Creator's address (indexed)contentHash- Content hash (indexed)ipfsCID- IPFS Content IdentifierassetType- Type of asset
Usage:
// Listen for new registrations
registry.on("CopyrightRegistered", (id, creator, contentHash, ipfsCID, assetType) => {
console.log(`New registration #${id} by ${creator}`);
});
// Query past events
const filter = registry.filters.CopyrightRegistered(null, creatorAddress);
const events = await registry.queryFilter(filter);// 1. Prepare content
const content = "My original artwork data";
const contentHash = ethers.id(content); // or use keccak256
// 2. Upload to IPFS (using Pinata, NFT.Storage, etc.)
const ipfsCID = await uploadToIPFS(fileBuffer);
// 3. Register copyright
const tx = await registry.registerCopyright(
contentHash,
ipfsCID,
"Sunset Over Jakarta",
"Digital painting of Jakarta skyline at sunset",
0, // Art
["digital", "painting", "jakarta", "sunset"]
);
const receipt = await tx.wait();
const event = receipt.events.find(e => e.event === "CopyrightRegistered");
const registrationId = event.args.id;
console.log(`Registered with ID: ${registrationId}`);
// 4. Verify registration
const registration = await registry.getRegistration(registrationId);
console.log(`Creator: ${registration.creator}`);
console.log(`Timestamp: ${new Date(registration.timestamp * 1000)}`);// Prevent duplicate registration
const contentHash = ethers.id(myContent);
if (await registry.isContentRegistered(contentHash)) {
const existingReg = await registry.getRegistrationByHash(contentHash);
console.log(`Already registered by ${existingReg.creator}`);
console.log(`On ${new Date(existingReg.timestamp * 1000)}`);
} else {
// Proceed with registration
await registry.registerCopyright(...);
}// Get all works by a creator
const creatorAddress = "0x...";
const workIds = await registry.getRegistrationsByCreator(creatorAddress);
// Fetch details for each work
for (const id of workIds) {
const work = await registry.getRegistration(id);
console.log(`${work.title} - ${work.assetType}`);
}Test Network: Hardhat Local Compiler: Solidity 0.8.20 with optimization (200 runs)
| Operation | Gas Cost | Notes |
|---|---|---|
registerCopyright |
~412,000 | With 2 tags, typical metadata |
getRegistration |
~5,000 | View function, minimal cost |
isContentRegistered |
~3,000 | Simple storage lookup |
getRegistrationsByCreator |
Varies | Depends on array length |
Optimization Notes:
- Storage-heavy operations (arrays, strings) increase gas
- Use minimal tags and descriptions to reduce costs
- On Sepolia, costs will vary based on network congestion
- Estimated Sepolia cost: ~0.0004 ETH per registration (at 1 gwei)
- No admin functions - fully decentralized
- Only creator can register under their address
- All data is public and immutable
- ✅ Empty content hash rejected
- ✅ Empty title rejected
- ✅ Empty IPFS CID rejected
- ✅ Duplicate content hash rejected
- ✅ Invalid registration ID reverts
- Always check
isContentRegistered()before attempting to register - Store IPFS CID on a pinning service (Pinata, NFT.Storage) to ensure availability
- Use content hashing consistently (same method for verification)
- Keep IPFS metadata accessible for certificate generation
Test Suite: test/CopyrightRegistry.test.js
Total Tests: 19
Coverage: 100% statements, 100% functions, 100% lines, 78.57% branches
Test Categories:
- Deployment (2 tests)
- Copyright Registration (5 tests)
- Asset Type Classification (2 tests)
- Content Verification (3 tests)
- Query Functions (4 tests)
- Creator Verification (2 tests)
- Gas Optimization (1 test)
Run Tests:
npm test
npm run coverage # For coverage reportPlanned for post-hackathon:
- Batch Registration - Register multiple works in one transaction
- Update Metadata - Allow creators to update non-critical fields
- Transfer Ownership - Enable copyright transfers
- Collaborative Works - Multi-owner support with percentage splits
- Licensing Terms - Attach standard licenses (CC, All Rights Reserved)
Document Version: 1.0 Last Updated: October 13, 2025 Maintained By: Karya Chain Team