···11// SPDX-License-Identifier: MIT
22pragma solidity 0.8.28;
3344+/// @title CowRegistry
55+/// @notice On-chain registry for the did:cow (Consensus Ownership Wrapper) DID method.
66+/// @dev A did:cow DID is identified by its initial controller address and initial wrapped DID
77+/// (stored without the leading "did:" prefix, e.g. "plc:abc123" or "web:example.com").
88+/// Creation is off-chain and free. This contract only needs to be called when migrating
99+/// to a new wrapped DID, transferring control, or deactivating.
410contract CowRegistry {
5111212+ /// @notice On-chain state for a single did:cow identifier.
1313+ /// @dev The cowHash key is derived from the *initial* controller and wrapped DID,
1414+ /// so these fields reflect the *current* state after any updates.
615 struct Cow {
716 address controller;
817 string wrappedDID;
918 }
10192020+ /// @dev Sentinel value stored in wrappedDID to mark a deactivated cow.
1121 string constant DEACTIVATED = ":";
12222323+ /// @notice Mapping from cow hash to current on-chain state.
2424+ /// @dev Returns zero values if the cow has never been registered on-chain.
1325 mapping(bytes32 => Cow) public cows;
14262727+ /// @notice Emitted when a cow is registered on-chain for the first time.
1528 event CowInitialized(bytes32 indexed cowHash, address controller, string wrappedDID);
2929+3030+ /// @notice Emitted when a cow is permanently deactivated.
1631 event CowDeactivated(bytes32 indexed cowHash);
3232+3333+ /// @notice Emitted when a cow's controller address is updated.
1734 event ControllerUpdated(bytes32 indexed cowHash, address controller);
3535+3636+ /// @notice Emitted when a cow's wrapped DID is updated.
1837 event WrappedDIDUpdated(bytes32 indexed cowHash, string wrappedDID);
19383939+ /// @notice Update the wrapped DID for an already-registered cow.
4040+ /// @dev Caller must be the current controller. Use updateWrappedDID if the cow
4141+ /// may not yet be registered on-chain.
4242+ /// @param _cowHash The cow's registry key, as returned by calculateCowHash.
4343+ /// @param _wrappedDID The new wrapped DID, without the leading "did:" prefix.
2044 function updateWrappedDIDByHash(bytes32 _cowHash, string memory _wrappedDID) public {
2145 require(msg.sender == cows[_cowHash].controller);
2246 require(bytes(_wrappedDID).length > 1, "Use deactivate() to deactivate");
···2549 emit WrappedDIDUpdated(_cowHash, _wrappedDID);
2650 }
27515252+ /// @notice Transfer control of an already-registered cow to a new address.
5353+ /// @dev Caller must be the current controller. Use updateController if the cow
5454+ /// may not yet be registered on-chain.
5555+ /// Setting _controller to address(0) makes the cow permanently uncontrollable
5656+ /// without deactivating it — it will continue to resolve but can never be updated.
5757+ /// @param _cowHash The cow's registry key, as returned by calculateCowHash.
5858+ /// @param _controller The new controller address.
2859 function updateControllerByHash(bytes32 _cowHash, address _controller) public {
2960 require(msg.sender == cows[_cowHash].controller);
3061···3263 emit ControllerUpdated(_cowHash, _controller);
3364 }
34656666+ /// @notice Permanently deactivate an already-registered cow.
6767+ /// @dev Caller must be the current controller. Deactivation is irreversible.
6868+ /// Use deactivate if the cow may not yet be registered on-chain.
6969+ /// @param _cowHash The cow's registry key, as returned by calculateCowHash.
3570 function deactivateByHash(bytes32 _cowHash) public {
3671 require(msg.sender == cows[_cowHash].controller);
3772···4176 emit CowDeactivated(_cowHash);
4277 }
43787979+ /// @notice Derive the registry key for a did:cow identifier.
8080+ /// @param _controller The initial controller address.
8181+ /// @param _wrappedDID The initial wrapped DID, without the leading "did:" prefix.
8282+ /// @return The keccak256 hash used as the key in the cows mapping.
4483 function calculateCowHash(address _controller, string memory _wrappedDID) public pure returns (bytes32) {
4584 return keccak256(abi.encodePacked(_controller, _wrappedDID));
4685 }
47868787+ /// @dev Register a cow on-chain if not already present, then return its hash.
4888 function _ensureCowInitialized(address _controller, string memory _wrappedDID) internal returns (bytes32 cowHash) {
4989 cowHash = calculateCowHash(_controller, _wrappedDID);
5090 if (bytes(cows[cowHash].wrappedDID).length == 0) {
···5494 return cowHash;
5595 }
56969797+ /// @notice Resolve a did:cow identifier to its current controller and wrapped DID.
9898+ /// @dev Returns the initial values if the cow has never been registered on-chain.
9999+ /// Prepend "did:" to the returned wrappedDID to form the full wrapped DID string.
100100+ /// @param _controller The initial controller address from the did:cow identifier.
101101+ /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:".
102102+ /// @return controller The current controller address.
103103+ /// @return wrappedDID The current wrapped DID (without "did:"), or ":" if deactivated.
57104 function resolveCow(address _controller, string memory _wrappedDID) external view returns (address controller, string memory wrappedDID) {
58105 bytes32 cowHash = calculateCowHash(_controller, _wrappedDID);
59106 Cow storage cow = cows[cowHash];
60107 if (bytes(cow.wrappedDID).length == 0) {
6161- // Not yet on-chain — initial values are authoritative
62108 return (_controller, _wrappedDID);
63109 }
64110 return (cow.controller, cow.wrappedDID);
65111 }
661126767- // You don't particularly need to call this, you can leave it until you make an update
113113+ /// @notice Optionally pre-register a cow on-chain before its first update.
114114+ /// @dev This is never strictly necessary — updateWrappedDID, updateController, and
115115+ /// deactivate all register the cow automatically if needed.
116116+ /// @param _controller The initial controller address.
117117+ /// @param _wrappedDID The initial wrapped DID, without the leading "did:" prefix.
68118 function initializeCow(address _controller, string memory _wrappedDID) external {
69119 _ensureCowInitialized(_controller, _wrappedDID);
70120 }
71121122122+ /// @notice Update the wrapped DID, registering the cow on-chain if not already present.
123123+ /// @param _controller The initial controller address from the did:cow identifier.
124124+ /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:".
125125+ /// @param _newWrappedDID The new wrapped DID, without the leading "did:" prefix.
72126 function updateWrappedDID(address _controller, string memory _wrappedDID, string memory _newWrappedDID) external {
73127 bytes32 cowHash = _ensureCowInitialized(_controller, _wrappedDID);
74128 updateWrappedDIDByHash(cowHash, _newWrappedDID);
75129 }
76130131131+ /// @notice Transfer control to a new address, registering the cow on-chain if not already present.
132132+ /// @param _controller The initial controller address from the did:cow identifier.
133133+ /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:".
134134+ /// @param _newController The new controller address.
77135 function updateController(address _controller, string memory _wrappedDID, address _newController) external {
78136 bytes32 cowHash = _ensureCowInitialized(_controller, _wrappedDID);
79137 updateControllerByHash(cowHash, _newController);
80138 }
81139140140+ /// @notice Permanently deactivate a cow, registering it on-chain if not already present.
141141+ /// @dev Deactivation is irreversible.
142142+ /// @param _controller The initial controller address from the did:cow identifier.
143143+ /// @param _wrappedDID The initial wrapped DID from the did:cow identifier, without "did:".
82144 function deactivate(address _controller, string memory _wrappedDID) external {
83145 bytes32 cowHash = _ensureCowInitialized(_controller, _wrappedDID);
84146 deactivateByHash(cowHash);