Skip to content

Commit 55a82a7

Browse files
committed
badge WIP
1 parent 6acb0db commit 55a82a7

File tree

8 files changed

+744
-0
lines changed

8 files changed

+744
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
/// @title Minimal Soulbound NFT Interface
5+
/// @notice Minimal interface for soulbinding EIP-721 NFTs
6+
interface IERC5192 {
7+
/// @notice Emitted when the locking status is changed to locked.
8+
/// @dev If a token is minted and the status is locked, this event should be emitted.
9+
/// @param tokenId The identifier for a token.
10+
event Locked(uint256 tokenId);
11+
12+
/// @notice Emitted when the locking status is changed to unlocked.
13+
/// @dev If a token is minted and the status is unlocked, this event should be emitted.
14+
/// @param tokenId The identifier for a token.
15+
event Unlocked(uint256 tokenId);
16+
17+
/// @notice Returns the locking status of an Soulbound Token
18+
/// @dev SBTs assigned to zero address are considered invalid, and queries
19+
/// about them do throw.
20+
/// @param tokenId The identifier for an SBT.
21+
function locked(uint256 tokenId) external view returns (bool);
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
5+
6+
/// @title Organization NFT Interface
7+
/// @dev An organization NFT represents a Story ecosystem partner.
8+
/// The root organization NFT represents Story.
9+
/// Each organization NFT register as a IP on Story and is a derivative of the root organization IP.
10+
interface IOrgNFT is IERC721Metadata {
11+
////////////////////////////////////////////////////////////////////////////
12+
// Errors //
13+
////////////////////////////////////////////////////////////////////////////
14+
15+
/// @notice Zero address provided as a param to OrgNFT functions.
16+
error OrgNFT__ZeroAddressParam();
17+
18+
/// @notice Root organization NFT has not been minted yet (`mintRootOrgNft` has not been called).
19+
error OrgNFT__RootOrgNftNotMinted();
20+
21+
/// @notice Caller is not the StoryNFTFactory contract.
22+
error OrgNFT__CallerNotStoryNFTFactory(address caller, address storyNftFactory);
23+
24+
////////////////////////////////////////////////////////////////////////////
25+
// Events //
26+
////////////////////////////////////////////////////////////////////////////
27+
28+
/// @notice Emitted when a organization NFT minted.
29+
/// @param recipient The address of the recipient of the organization NFT.
30+
/// @param tokenId The ID of the minted organization NFT.
31+
/// @param orgIpId The ID of the organization IP.
32+
event OrgNFTMinted(address recipient, uint256 tokenId, address orgIpId);
33+
34+
////////////////////////////////////////////////////////////////////////////
35+
// Functions //
36+
////////////////////////////////////////////////////////////////////////////
37+
38+
/// @notice Mints the root organization NFT and register it as an IP.
39+
/// @param recipient The address of the recipient of the root organization NFT.
40+
/// @param tokenURI The URI of the root organization NFT.
41+
/// @return rootOrgTokenId The ID of the root organization NFT.
42+
/// @return rootOrgIpId The ID of the root organization IP.
43+
function mintRootOrgNft(
44+
address recipient,
45+
string memory tokenURI
46+
) external returns (uint256 rootOrgTokenId, address rootOrgIpId);
47+
48+
/// @notice Mints a organization NFT, register it as an IP,
49+
/// and makes the IP as a derivative of the root organization IP.
50+
/// @param recipient The address of the recipient of the minted organization NFT.
51+
/// @param tokenURI The URI of the minted organization NFT.
52+
/// @return orgTokenId The ID of the minted organization NFT.
53+
/// @return orgIpId The ID of the organization IP.
54+
function mintOrgNft(
55+
address recipient,
56+
string memory tokenURI
57+
) external returns (uint256 orgTokenId, address orgIpId);
58+
59+
/// @notice Returns the ID of the root organization IP.
60+
function getRootOrgIpId() external view returns (address);
61+
62+
/// @notice Returns the total supply of OrgNFT.
63+
function totalSupply() external view returns (uint256);
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
5+
6+
import { IERC5192 } from "./IERC5192.sol";
7+
8+
/// @title Story Badge Interface
9+
/// @dev A Story Badge is a soulbound NFT that has a unified token URI for all badges.
10+
interface IStoryBadge is IERC5192, IERC721Metadata {
11+
////////////////////////////////////////////////////////////////////////////
12+
// Errors //
13+
////////////////////////////////////////////////////////////////////////////
14+
15+
/// @notice Zero address provided as a param to StoryBadge functions.
16+
error StoryBadge__ZeroAddressParam();
17+
18+
/// @notice Badges are soulbound, cannot be transferred.
19+
error StoryBadge__TransferLocked();
20+
21+
/// @notice Invalid whitelist signature.
22+
error StoryBadge__InvalidSignature();
23+
24+
/// @notice The provided whitelist signature is already used.
25+
error StoryBadge__SignatureAlreadyUsed();
26+
27+
////////////////////////////////////////////////////////////////////////////
28+
// Structs //
29+
////////////////////////////////////////////////////////////////////////////
30+
31+
/// @notice Struct for initializing the StoryBadge contract.
32+
/// @param name The name of the collection.
33+
/// @param symbol The symbol of the collection.
34+
/// @param contractURI The contract URI of the collection (follows OpenSea contract-level metadata standard).
35+
/// @param tokenURI The token URI for all the badges (follows OpenSea metadata standard).
36+
/// @param signer The signer of the whitelist signatures.
37+
/// @param ipAssetRegistry Story Proof-of-Creativity IP Asset Registry address.
38+
/// @param licensingModule Story Proof-of-Creativity Licensing Module address.
39+
/// @param piLicenseTemplate Story Proot-of-Creativity Programmable IP License Template address.
40+
/// @param defaultLicenseTermsId Story Proot-of-Creativity default license terms ID.
41+
struct InitParams {
42+
string name;
43+
string symbol;
44+
string contractURI;
45+
string tokenURI;
46+
address signer;
47+
address ipAssetRegistry;
48+
address licensingModule;
49+
address piLicenseTemplate;
50+
uint256 defaultLicenseTermsId;
51+
}
52+
53+
////////////////////////////////////////////////////////////////////////////
54+
// Events //
55+
////////////////////////////////////////////////////////////////////////////
56+
57+
/// @notice Emitted when a badge is minted.
58+
/// @param recipient The address of the recipient of the badge.
59+
/// @param tokenId The token ID of the minted badge.
60+
/// @param ipId The ID of the badge IP.
61+
event StoryBadgeMinted(address recipient, uint256 tokenId, address ipId);
62+
63+
/// @notice Emitted when the signer is updated.
64+
/// @param signer The new signer address.
65+
event StoryBadgeSingerUpdated(address signer);
66+
67+
/// @notice Emitted when the token URI is updated.
68+
/// @param tokenURI The new token URI.
69+
event StoryBadgeTokenURIUpdated(string tokenURI);
70+
71+
////////////////////////////////////////////////////////////////////////////
72+
// Functions //
73+
////////////////////////////////////////////////////////////////////////////
74+
75+
/// @notice Mints a badge for the given recipient.
76+
/// @param to The address of the recipient of the badge.
77+
/// @param signature The signature from the whitelist signer.
78+
/// @return tokenId The token ID of the minted badge.
79+
/// @return ipId The ID of the badge IP.
80+
function mint(address to, bytes calldata signature) external returns (uint256 tokenId, address ipId);
81+
82+
/// @notice Updates the whitelist signer.
83+
/// @param signer_ The new whitelist signer address.
84+
function setSigner(address signer_) external;
85+
86+
/// @notice Updates the unified token URI for all badges.
87+
/// @param tokenURI_ The new token URI.
88+
function setTokenURI(string memory tokenURI_) external;
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
/// @title Story NFT Factory Interface
5+
///
6+
interface IStoryNFTFactory {
7+
////////////////////////////////////////////////////////////////////////////
8+
// Errors //
9+
////////////////////////////////////////////////////////////////////////////
10+
error StoryNFTFactory__ZeroAddressParam();
11+
error StoryNFTFactory__InvalidSignature(bytes signature);
12+
error StoryNFTFactory__NftTemplateNotWhitelisted(address nftTemplate);
13+
error StoryNFTFactory__SignatureAlreadyUsed(bytes signature);
14+
error StoryNFTFactory__OrgNotFound(string orgName);
15+
error StoryNFTFactory__OrgAlreadyDeployed(string orgName);
16+
17+
////////////////////////////////////////////////////////////////////////////
18+
// Events //
19+
////////////////////////////////////////////////////////////////////////////
20+
event StoryNftDeployed(string orgName, address orgNft, uint256 orgTokenId, address orgIpId, address storyNft);
21+
event StoryNFTFactorySignerUpdated(address signer);
22+
event StoryNFTFactoryNftTemplateWhitelisted(address nftTemplate);
23+
24+
////////////////////////////////////////////////////////////////////////////
25+
// Functions //
26+
////////////////////////////////////////////////////////////////////////////
27+
function deployStoryNft(
28+
address storyNftTemplate,
29+
address storyNftOwner,
30+
string calldata orgName,
31+
string calldata orgTokenURI,
32+
bytes calldata signature,
33+
bytes calldata initData
34+
) external returns (address orgNft, uint256 orgTokenId, address orgIpId, address storyNft);
35+
36+
function setSigner(address signer) external;
37+
function whitelistNftTemplate(address storyNftTemplate) external;
38+
39+
function getDefaultStoryNftTemplate() external view returns (address);
40+
function getStoryNftAddress(string calldata orgName) external view returns (address storyNft);
41+
}
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
// solhint-disable-next-line max-line-length
5+
import { ERC721URIStorageUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
6+
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
7+
import { IIPAssetRegistry } from "@story-protocol/protocol-core/contracts/interfaces/registries/IIPAssetRegistry.sol";
8+
/*solhint-disable-next-line max-line-length*/
9+
import { ILicensingModule } from "@story-protocol/protocol-core/contracts/interfaces/modules/licensing/ILicensingModule.sol";
10+
11+
abstract contract BaseStoryNFT is ERC721URIStorageUpgradeable, OwnableUpgradeable {
12+
error BaseStoryNFT__ZeroAddressParam();
13+
14+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
15+
IIPAssetRegistry public immutable IP_ASSET_REGISTRY;
16+
ILicensingModule public immutable LICENSING_MODULE;
17+
18+
string private _contractURI;
19+
string private _baseURI_;
20+
uint256 private _totalSupply;
21+
22+
function initialize(
23+
address initialOwner,
24+
address orgNft,
25+
uint256 orgTokenId,
26+
address orgIpId,
27+
bytes calldata initData
28+
) public virtual;
29+
30+
function __BaseStoryNFT_init(
31+
string memory name_,
32+
string memory symbol_,
33+
string memory contractURI_,
34+
string memory baseURI_,
35+
address ipAssetRegistry,
36+
address licensingModule
37+
) internal onlyInitializing {
38+
if (ipAssetRegistry == address(0) || licensingModule == address(0)) revert BaseStoryNFT__ZeroAddressParam();
39+
__ERC721_init(name_, symbol_);
40+
_contractURI = contractURI_;
41+
_baseURI_ = baseURI_;
42+
IP_ASSET_REGISTRY = IIPAssetRegistry(ipAssetRegistry);
43+
LICENSING_MODULE = ILicensingModule(licensingModule);
44+
_totalSupply = 0;
45+
}
46+
47+
function _mintAndRegisterIp(address to) internal virtual returns (uint256 tokenId, address ipId) {
48+
(tokenId, ipId) = _mintAndRegisterIp(to, "");
49+
}
50+
51+
function _mintAndRegisterIp(
52+
address to,
53+
string memory tokenURI
54+
) internal virtual returns (uint256 tokenId, address ipId) {
55+
tokenId = _totalSupply++;
56+
_safeMint(to, tokenId);
57+
_setTokenURI(tokenId, tokenURI);
58+
ipId = IP_ASSET_REGISTRY.register(block.chainid, address(this), tokenId);
59+
}
60+
61+
function _makeDerivative(
62+
address childIpId,
63+
address[] memory parentIpIds,
64+
address licenseTemplate,
65+
uint256[] memory licenseTermsIds,
66+
bytes memory royaltyContext
67+
) internal virtual {
68+
LICENSING_MODULE.registerDerivative({
69+
childIpId: childIpId,
70+
parentIpIds: parentIpIds,
71+
licenseTermsIds: licenseTermsIds,
72+
licenseTemplate: licenseTemplate,
73+
royaltyContext: royaltyContext
74+
});
75+
}
76+
77+
function _baseURI() internal view virtual override returns (string memory) {
78+
return _baseURI_;
79+
}
80+
81+
function totalSupply() external view returns (uint256) {
82+
return _totalSupply;
83+
}
84+
85+
function contractURI() external view virtual returns (string memory) {
86+
return _contractURI;
87+
}
88+
}

0 commit comments

Comments
 (0)