1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.27;
3
4import {IEAS} from "./interfaces/IEAS.sol";
5import {IPAssetAuth} from "./IPAssetAuth.sol";
6
7/// @notice Tracks transferable rights and their on-chain ownership history.
8/// @dev Each right is identified by a deterministic rightId derived from
9/// keccak256(recordId, rightType). Right types match the music-rights
10/// schema enum: 0 master_economic_interest, 1 catalog_administration,
11/// 2 publishing_administration, 3 neighboring_rights, 4 sync_clearance,
12/// 5 mechanical_administration.
13contract RightsRegistry {
14 IEAS public immutable eas;
15 IPAssetAuth public immutable auth;
16
17 struct Right {
18 address currentOwner;
19 bytes32 subLabel;
20 uint8 rightType;
21 uint64 lastTransferAt;
22 bool exists;
23 }
24
25 mapping(bytes32 => Right) public rights;
26
27 event RightRegistered(
28 bytes32 indexed rightId,
29 bytes32 indexed subLabel,
30 uint8 indexed rightType,
31 address initialOwner,
32 bytes32 attestationUID
33 );
34
35 event RightsTransferred(
36 bytes32 indexed rightId,
37 address indexed from,
38 address indexed to,
39 uint8 rightType,
40 bytes32 attestationUID,
41 uint64 timestamp
42 );
43
44 /// @notice Compute the deterministic right ID for a (recordId, rightType).
45 function rightIdOf(string memory recordId, uint8 rightType) public pure returns (bytes32) {
46 return keccak256(abi.encodePacked(recordId, rightType));
47 }
48
49 /// @notice Register a right for the first time. Caller must hold
50 /// ROLE_REGISTER for the sub-label; attestationUID references the
51 /// MUSIC_RECORDING_v1 attestation for the underlying recording.
52 function registerRight(
53 string calldata recordId,
54 bytes32 subLabel,
55 uint8 rightType,
56 address initialOwner,
57 bytes32 attestationUID
58 ) external returns (bytes32 rightId) {
59 require(auth.canRegister(msg.sender, subLabel), "RightsRegistry: not authorized");
60 require(rightType <= 5, "RightsRegistry: bad rightType");
61 _verifyAttestationExists(attestationUID);
62
63 rightId = rightIdOf(recordId, rightType);
64 require(!rights[rightId].exists, "RightsRegistry: already registered");
65
66 rights[rightId] = Right({
67 currentOwner: initialOwner,
68 subLabel: subLabel,
69 rightType: rightType,
70 lastTransferAt: uint64(block.timestamp),
71 exists: true
72 });
73
74 emit RightRegistered(rightId, subLabel, rightType, initialOwner, attestationUID);
75 }
76
77 /// @notice Transfer a right. Caller must be the current owner. The
78 /// attestation must be a RIGHTS_TRANSFER_v1 with both signatures.
79 function transferRight(bytes32 rightId, address to, bytes32 attestationUID) external {
80 Right storage r = rights[rightId];
81 require(r.exists, "RightsRegistry: unknown right");
82 require(msg.sender == r.currentOwner, "RightsRegistry: not current owner");
83 _verifyAttestationExists(attestationUID);
84
85 address from = r.currentOwner;
86 r.currentOwner = to;
87 r.lastTransferAt = uint64(block.timestamp);
88
89 emit RightsTransferred(rightId, from, to, r.rightType, attestationUID, uint64(block.timestamp));
90 }
91
92 function ownerOf(bytes32 rightId) external view returns (address) {
93 require(rights[rightId].exists, "RightsRegistry: unknown right");
94 return rights[rightId].currentOwner;
95 }
96}