Skip to content

Commit 4c5bc74

Browse files
authored
feat: add identity registry for agent promotion to be discoverable (#4496)
### Description Added an Identity Registry facet to the AppRegistry diamond, implementing ERC-8004 compliant agent identity management. This allows apps to register as agents with metadata and URIs, creating a decentralized identity system for apps in the Towns ecosystem. ### Changes - Added `IdentityRegistryFacet` with registration, metadata management, and URI functionality - Created interfaces and storage contracts for the identity registry system - Integrated the identity registry with the AppRegistry diamond deployment - Added a `publish` method to SimpleApp to register as an agent - Added tests for agent publishing functionality ### Checklist - [x] Tests added where required - [x] Documentation updated where applicable - [x] Changes adhere to the repository's contribution guidelines
1 parent 9e77ff9 commit 4c5bc74

File tree

14 files changed

+1551
-100
lines changed

14 files changed

+1551
-100
lines changed

packages/contracts/scripts/deployments/diamonds/DeployAppRegistry.s.sol

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pragma solidity ^0.8.23;
33

44
// interfaces
55
import {IDiamondInitHelper} from "./IDiamondInitHelper.sol";
6-
import {IAppFactory, IAppFactoryBase} from "src/apps/facets/factory/IAppFactory.sol";
6+
import {IAppFactoryBase} from "src/apps/facets/factory/IAppFactory.sol";
77

88
// libraries
99
import {DeployDiamondCut} from "@towns-protocol/diamond/scripts/deployments/facets/DeployDiamondCut.sol";
@@ -17,6 +17,7 @@ import {DeployAppInstallerFacet} from "../facets/DeployAppInstallerFacet.s.sol";
1717
import {DeployAppFactoryFacet} from "../facets/DeployAppFactoryFacet.s.sol";
1818
import {DeploySpaceFactory} from "../diamonds/DeploySpaceFactory.s.sol";
1919
import {DeploySimpleAppBeacon} from "../diamonds/DeploySimpleAppBeacon.s.sol";
20+
import {DeployIdentityRegistry} from "../facets/DeployIdentityRegistry.s.sol";
2021

2122
// contracts
2223
import {Diamond} from "@towns-protocol/diamond/src/Diamond.sol";
@@ -101,6 +102,7 @@ contract DeployAppRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
101102
facetHelper.add("AppRegistryFacet");
102103
facetHelper.add("AppInstallerFacet");
103104
facetHelper.add("AppFactoryFacet");
105+
facetHelper.add("IdentityRegistryFacet");
104106

105107
facetHelper.deployBatch(deployer);
106108

@@ -131,6 +133,13 @@ contract DeployAppRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
131133
DeployAppFactoryFacet.makeInitData(beacons, _getEntryPoint())
132134
);
133135

136+
facet = facetHelper.getDeployedAddress("IdentityRegistryFacet");
137+
addFacet(
138+
makeCut(facet, FacetCutAction.Add, DeployIdentityRegistry.selectors()),
139+
facet,
140+
DeployIdentityRegistry.makeInitData()
141+
);
142+
134143
address multiInit = facetHelper.getDeployedAddress("MultiInit");
135144

136145
return
@@ -166,6 +175,9 @@ contract DeployAppRegistry is IDiamondInitHelper, DiamondHelper, Deployer {
166175
if (facetName.eq("AppFactoryFacet")) {
167176
addCut(makeCut(facet, FacetCutAction.Add, DeployAppFactoryFacet.selectors()));
168177
}
178+
if (facetName.eq("IdentityRegistryFacet")) {
179+
addCut(makeCut(facet, FacetCutAction.Add, DeployIdentityRegistry.selectors()));
180+
}
169181
}
170182

171183
return baseFacets();
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
// interfaces
5+
import {IDiamond} from "@towns-protocol/diamond/src/IDiamond.sol";
6+
7+
// libraries
8+
import {LibDeploy} from "@towns-protocol/diamond/src/utils/LibDeploy.sol";
9+
import {DeployERC721A} from "../facets/DeployERC721A.s.sol";
10+
11+
// contracts
12+
import {IdentityRegistryFacet} from "src/apps/facets/identity/IdentityRegistryFacet.sol";
13+
import {DynamicArrayLib} from "solady/utils/DynamicArrayLib.sol";
14+
15+
library DeployIdentityRegistry {
16+
using DynamicArrayLib for DynamicArrayLib.DynamicArray;
17+
18+
function selectors() internal pure returns (bytes4[] memory res) {
19+
uint256 selectorsCount = 20;
20+
DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(selectorsCount);
21+
arr.p(bytes4(keccak256("register()")));
22+
arr.p(bytes4(keccak256("register(string)")));
23+
arr.p(bytes4(keccak256("register(string,(string,bytes)[])")));
24+
arr.p(IdentityRegistryFacet.getMetadata.selector);
25+
arr.p(IdentityRegistryFacet.setMetadata.selector);
26+
arr.p(IdentityRegistryFacet.setAgentUri.selector);
27+
28+
{
29+
bytes4[] memory selectors_2 = DeployERC721A.selectors();
30+
if (selectors_2.length > selectorsCount) {
31+
revert("Selectors count is greater than the reserved space");
32+
}
33+
for (uint256 i; i < selectors_2.length; ++i) {
34+
arr.p(selectors_2[i]);
35+
}
36+
}
37+
38+
bytes32[] memory selectors_ = arr.asBytes32Array();
39+
40+
assembly ("memory-safe") {
41+
res := selectors_
42+
}
43+
}
44+
45+
function makeCut(
46+
address facetAddress,
47+
IDiamond.FacetCutAction action
48+
) internal pure returns (IDiamond.FacetCut memory) {
49+
return
50+
IDiamond.FacetCut({
51+
action: action,
52+
facetAddress: facetAddress,
53+
functionSelectors: selectors()
54+
});
55+
}
56+
57+
function makeInitData() internal pure returns (bytes memory) {
58+
return abi.encodeCall(IdentityRegistryFacet.__IdentityRegistryFacet_init, ());
59+
}
60+
61+
function deploy() internal returns (address) {
62+
return LibDeploy.deployCode("IdentityRegistryFacet.sol", "");
63+
}
64+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
// interfaces
5+
6+
// libraries
7+
8+
// contracts
9+
10+
/// @title IIdentityRegistryBase
11+
/// @notice Base interface for ERC-8004 compliant agent identity registry
12+
/// @dev Defines core data structures, events, and errors for agent registration and metadata management
13+
interface IIdentityRegistryBase {
14+
/// @notice Metadata entry for storing additional on-chain agent information
15+
/// @dev Used in batch metadata operations during agent registration
16+
struct MetadataEntry {
17+
/// @notice The metadata key identifier
18+
string metadataKey;
19+
/// @notice The metadata value stored as bytes
20+
bytes metadataValue;
21+
}
22+
23+
/// @notice Emitted when a new agent is registered in the identity registry
24+
/// @param agentId The unique identifier (agentId) assigned to the agent
25+
/// @param agentUri The URI pointing to the agent's registration file
26+
/// @param owner The address that owns the agent identity
27+
event Registered(uint256 indexed agentId, string agentUri, address indexed owner);
28+
29+
/// @notice Emitted when metadata is set or updated for an agent
30+
/// @param agentId The unique identifier of the agent
31+
/// @param indexedKey The metadata key as indexed parameter for efficient filtering
32+
/// @param metadataKey The metadata key as non-indexed parameter for reading
33+
/// @param metadataValue The metadata value stored as bytes
34+
event MetadataSet(
35+
uint256 indexed agentId,
36+
string indexed indexedKey,
37+
string metadataKey,
38+
bytes metadataValue
39+
);
40+
41+
/// @notice Emitted when an agent's URI is updated
42+
/// @param agentId The unique identifier of the agent
43+
/// @param agentUri The new URI pointing to the agent's registration file
44+
/// @param updatedBy The address that performed the update
45+
event UriUpdated(uint256 indexed agentId, string agentUri, address indexed updatedBy);
46+
47+
/// @notice Thrown when caller is not authorized to perform the operation
48+
/// @dev Authorization requires being the owner, an approved operator, or approved for the specific token
49+
error IdentityRegistry__NotAuthorized();
50+
51+
/// @notice Thrown when attempting to access an agent that does not exist
52+
error IdentityRegistry__AgentDoesNotExist();
53+
54+
/// @notice Thrown when attempting an operation on an agent that is not registered
55+
error IdentityRegistry__AgentNotRegistered();
56+
57+
/// @notice Thrown when attempting to register an agent that is banned
58+
error IdentityRegistry__AgentBanned();
59+
60+
/// @notice Thrown when attempting to promote or register an agent that already has a promoted identity
61+
error IdentityRegistry__AgentAlreadyPromoted();
62+
}
63+
64+
/// @title IIdentityRegistry
65+
/// @notice ERC-8004 compliant agent identity registry interface
66+
/// @dev Extends ERC-721 with agent registration, metadata management, and URI storage capabilities.
67+
/// Each agent is uniquely identified globally by: namespace (eip155), chainId, identityRegistry address, and agentId.
68+
/// The tokenURI resolves to an agent registration file containing endpoints, capabilities, and trust model information.
69+
interface IIdentityRegistry is IIdentityRegistryBase {
70+
/// @notice Register a new agent identity without initial URI or metadata
71+
/// @dev Mints a new ERC-721 token to msg.sender. URI can be set later via setAgentUri.
72+
/// @return agentId The unique identifier assigned to the newly registered agent
73+
function register() external returns (uint256 agentId);
74+
75+
/// @notice Register a new agent identity with a tokenURI
76+
/// @dev Mints a new ERC-721 token to msg.sender and sets the tokenURI
77+
/// @param agentUri The URI pointing to the agent's registration file (e.g., ipfs://cid or https://domain.com/agent.json)
78+
/// @return agentId The unique identifier assigned to the newly registered agent
79+
function register(string calldata agentUri) external returns (uint256 agentId);
80+
81+
/// @notice Register a new agent identity with tokenURI and metadata
82+
/// @dev Mints a new ERC-721 token to msg.sender, sets the tokenURI, and stores metadata entries.
83+
/// This is the most complete registration method for agents with immediate metadata.
84+
/// @param agentUri The URI pointing to the agent's registration file
85+
/// @param metadata Array of metadata entries to store as on-chain metadata
86+
/// @return agentId The unique identifier assigned to the newly registered agent
87+
function register(
88+
string calldata agentUri,
89+
MetadataEntry[] calldata metadata
90+
) external returns (uint256 agentId);
91+
92+
/// @notice Retrieve metadata value for a specific agent and metadataKey
93+
/// @dev Returns empty bytes if the metadataKey does not exist
94+
/// @param agentId The unique identifier of the agent
95+
/// @param metadataKey The metadata key to retrieve
96+
/// @return The metadata value stored as bytes
97+
function getMetadata(
98+
uint256 agentId,
99+
string memory metadataKey
100+
) external view returns (bytes memory);
101+
102+
/// @notice Set or update metadata for a specific agent
103+
/// @dev Only callable by the agent owner, approved operator, or token-specific approved address.
104+
/// Emits MetadataSet event.
105+
/// @param agentId The unique identifier of the agent
106+
/// @param metadataKey The metadata key to set
107+
/// @param metadataValue The metadata value to store as bytes
108+
function setMetadata(
109+
uint256 agentId,
110+
string memory metadataKey,
111+
bytes memory metadataValue
112+
) external;
113+
114+
/// @notice Update the tokenURI for an agent
115+
/// @dev Only callable by the agent owner, approved operator, or token-specific approved address.
116+
/// Emits UriUpdated event. Useful when agent registration data changes.
117+
/// @param agentId The unique identifier of the agent
118+
/// @param agentUri The new URI pointing to the agent's updated registration file
119+
function setAgentUri(uint256 agentId, string calldata agentUri) external;
120+
121+
/// @notice Get the tokenURI for an agent
122+
/// @dev Returns the URI pointing to the agent's registration file.
123+
/// Reverts if the token does not exist.
124+
/// @param agentId The unique identifier of the agent (ERC-721 tokenId)
125+
/// @return The URI string pointing to the agent's registration file
126+
function tokenURI(uint256 agentId) external view returns (string memory);
127+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
// interfaces
5+
import {IIdentityRegistryBase} from "./IIdentityRegistry.sol";
6+
7+
// libraries
8+
import {IdentityRegistryStorage} from "./IdentityRegistryStorage.sol";
9+
10+
// contracts
11+
12+
abstract contract IdentityRegistryBase is IIdentityRegistryBase {
13+
function _setAgentUri(uint256 agentId, string memory agentUri) internal {
14+
IdentityRegistryStorage.getLayout().agentUri[agentId] = agentUri;
15+
}
16+
17+
function _getAgentUri(uint256 agentId) internal view returns (string memory) {
18+
return IdentityRegistryStorage.getLayout().agentUri[agentId];
19+
}
20+
21+
function _setMetadata(uint256 agentId, string memory metadataKey, bytes memory value) internal {
22+
IdentityRegistryStorage.getLayout().metadata[agentId][metadataKey] = value;
23+
emit MetadataSet(agentId, metadataKey, metadataKey, value);
24+
}
25+
26+
function _getMetadata(
27+
uint256 agentId,
28+
string memory metadataKey
29+
) internal view returns (bytes memory) {
30+
return IdentityRegistryStorage.getLayout().metadata[agentId][metadataKey];
31+
}
32+
}

0 commit comments

Comments
 (0)