Skip to content

Commit 8096551

Browse files
authored
feat: introduce RoyaltyTokenDistributionWorkflows (#126)
1 parent 52ee268 commit 8096551

15 files changed

+1193
-82
lines changed

contracts/SPGNFT.sol

+17-3
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,33 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
3939
bytes32 private constant SPGNFTStorageLocation = 0x66c08f80d8d0ae818983b725b864514cf274647be6eb06de58ff94d1defb6d00;
4040

4141
/// @dev The address of the DerivativeWorkflows contract.
42+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
4243
address public immutable DERIVATIVE_WORKFLOWS_ADDRESS;
4344

4445
/// @dev The address of the GroupingWorkflows contract.
46+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
4547
address public immutable GROUPING_WORKFLOWS_ADDRESS;
4648

4749
/// @dev The address of the LicenseAttachmentWorkflows contract.
50+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
4851
address public immutable LICENSE_ATTACHMENT_WORKFLOWS_ADDRESS;
4952

5053
/// @dev The address of the RegistrationWorkflows contract.
54+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
5155
address public immutable REGISTRATION_WORKFLOWS_ADDRESS;
5256

57+
/// @dev The address of the RoyaltyTokenDistributionWorkflows contract.
58+
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
59+
address public immutable ROYALTY_TOKEN_DISTRIBUTION_WORKFLOWS_ADDRESS;
60+
5361
/// @notice Modifier to restrict access to workflow contracts.
5462
modifier onlyPeriphery() {
5563
if (
5664
msg.sender != DERIVATIVE_WORKFLOWS_ADDRESS &&
5765
msg.sender != GROUPING_WORKFLOWS_ADDRESS &&
5866
msg.sender != LICENSE_ATTACHMENT_WORKFLOWS_ADDRESS &&
59-
msg.sender != REGISTRATION_WORKFLOWS_ADDRESS
67+
msg.sender != REGISTRATION_WORKFLOWS_ADDRESS &&
68+
msg.sender != ROYALTY_TOKEN_DISTRIBUTION_WORKFLOWS_ADDRESS
6069
) revert Errors.SPGNFT__CallerNotPeripheryContract();
6170
_;
6271
}
@@ -66,19 +75,22 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
6675
address derivativeWorkflows,
6776
address groupingWorkflows,
6877
address licenseAttachmentWorkflows,
69-
address registrationWorkflows
78+
address registrationWorkflows,
79+
address royaltyTokenDistributionWorkflows
7080
) {
7181
if (
7282
derivativeWorkflows == address(0) ||
7383
groupingWorkflows == address(0) ||
7484
licenseAttachmentWorkflows == address(0) ||
75-
registrationWorkflows == address(0)
85+
registrationWorkflows == address(0) ||
86+
royaltyTokenDistributionWorkflows == address(0)
7687
) revert Errors.SPGNFT__ZeroAddressParam();
7788

7889
DERIVATIVE_WORKFLOWS_ADDRESS = derivativeWorkflows;
7990
GROUPING_WORKFLOWS_ADDRESS = groupingWorkflows;
8091
LICENSE_ATTACHMENT_WORKFLOWS_ADDRESS = licenseAttachmentWorkflows;
8192
REGISTRATION_WORKFLOWS_ADDRESS = registrationWorkflows;
93+
ROYALTY_TOKEN_DISTRIBUTION_WORKFLOWS_ADDRESS = royaltyTokenDistributionWorkflows;
8294

8395
_disableInitializers();
8496
}
@@ -286,6 +298,8 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
286298
_grantRole(SPGNFTLib.MINTER_ROLE, LICENSE_ATTACHMENT_WORKFLOWS_ADDRESS);
287299
_grantRole(SPGNFTLib.ADMIN_ROLE, REGISTRATION_WORKFLOWS_ADDRESS);
288300
_grantRole(SPGNFTLib.MINTER_ROLE, REGISTRATION_WORKFLOWS_ADDRESS);
301+
_grantRole(SPGNFTLib.ADMIN_ROLE, ROYALTY_TOKEN_DISTRIBUTION_WORKFLOWS_ADDRESS);
302+
_grantRole(SPGNFTLib.MINTER_ROLE, ROYALTY_TOKEN_DISTRIBUTION_WORKFLOWS_ADDRESS);
289303
}
290304

291305
//
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
import { PILTerms } from "@storyprotocol/core/interfaces/modules/licensing/IPILicenseTemplate.sol";
5+
6+
import { WorkflowStructs } from "../../lib/WorkflowStructs.sol";
7+
8+
/// @title Royalty Token Distribution Workflows Interface
9+
/// @notice Interface for IP royalty token distribution workflows.
10+
interface IRoyaltyTokenDistributionWorkflows {
11+
/// @notice Mint an NFT and register the IP, attach PIL terms, and distribute royalty tokens.
12+
/// @dev In order to successfully distribute royalty tokens, the license terms attached to the IP must be
13+
/// a commercial license.
14+
/// @param spgNftContract The address of the SPG NFT contract.
15+
/// @param recipient The address to receive the NFT.
16+
/// @param ipMetadata The metadata for the IP.
17+
/// @param terms The PIL terms to attach to the IP (must be a commercial license).
18+
/// @param royaltyShares Authors of the IP and their shares of the royalty tokens, see {WorkflowStructs.RoyaltyShare}.
19+
/// @return ipId The ID of the registered IP.
20+
/// @return tokenId The ID of the minted NFT.
21+
/// @return licenseTermsId The ID of the attached PIL terms.
22+
function mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokens(
23+
address spgNftContract,
24+
address recipient,
25+
WorkflowStructs.IPMetadata calldata ipMetadata,
26+
PILTerms calldata terms,
27+
WorkflowStructs.RoyaltyShare[] calldata royaltyShares
28+
) external returns (address ipId, uint256 tokenId, uint256 licenseTermsId);
29+
30+
/// @notice Mint an NFT and register the IP, make a derivative, and distribute royalty tokens.
31+
/// @dev In order to successfully distribute royalty tokens, the license terms attached to the IP must be
32+
/// a commercial license.
33+
/// @param spgNftContract The address of the SPG NFT contract.
34+
/// @param recipient The address to receive the NFT.
35+
/// @param ipMetadata The metadata for the IP.
36+
/// @param derivData The data for the derivative, see {WorkflowStructs.MakeDerivative}.
37+
/// @param royaltyShares Authors of the IP and their shares of the royalty tokens, see {WorkflowStructs.RoyaltyShare}.
38+
/// @return ipId The ID of the registered IP.
39+
/// @return tokenId The ID of the minted NFT.
40+
function mintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens(
41+
address spgNftContract,
42+
address recipient,
43+
WorkflowStructs.IPMetadata calldata ipMetadata,
44+
WorkflowStructs.MakeDerivative calldata derivData,
45+
WorkflowStructs.RoyaltyShare[] calldata royaltyShares
46+
) external returns (address ipId, uint256 tokenId);
47+
48+
/// @notice Register an IP, attach PIL terms, and deploy a royalty vault.
49+
/// @dev In order to successfully deploy a royalty vault, the license terms attached to the IP must be
50+
/// a commercial license.
51+
/// @param nftContract The address of the NFT contract.
52+
/// @param tokenId The ID of the NFT.
53+
/// @param ipMetadata The metadata for the IP.
54+
/// @param terms The PIL terms to attach to the IP (must be a commercial license).
55+
/// @param sigMetadata The signature data for the IP metadata.
56+
/// @param sigAttach The signature data for attaching the PIL terms.
57+
/// @return ipId The ID of the registered IP.
58+
/// @return licenseTermsId The ID of the attached PIL terms.
59+
/// @return ipRoyaltyVault The address of the deployed royalty vault.
60+
function registerIpAndAttachPILTermsAndDeployRoyaltyVault(
61+
address nftContract,
62+
uint256 tokenId,
63+
WorkflowStructs.IPMetadata calldata ipMetadata,
64+
PILTerms calldata terms,
65+
WorkflowStructs.SignatureData calldata sigMetadata,
66+
WorkflowStructs.SignatureData calldata sigAttach
67+
) external returns (address ipId, uint256 licenseTermsId, address ipRoyaltyVault);
68+
69+
/// @notice Register an IP, make a derivative, and deploy a royalty vault.
70+
/// @dev In order to successfully deploy a royalty vault, the license terms attached to the IP must be
71+
/// a commercial license.
72+
/// @param nftContract The address of the NFT contract.
73+
/// @param tokenId The ID of the NFT.
74+
/// @param ipMetadata The metadata for the IP.
75+
/// @param derivData The data for the derivative, see {WorkflowStructs.MakeDerivative}.
76+
/// @param sigMetadata The signature data for the IP metadata.
77+
/// @param sigRegister The signature data for registering the derivative.
78+
/// @return ipId The ID of the registered IP.
79+
/// @return ipRoyaltyVault The address of the deployed royalty vault.
80+
function registerIpAndMakeDerivativeAndDeployRoyaltyVault(
81+
address nftContract,
82+
uint256 tokenId,
83+
WorkflowStructs.IPMetadata calldata ipMetadata,
84+
WorkflowStructs.MakeDerivative calldata derivData,
85+
WorkflowStructs.SignatureData calldata sigMetadata,
86+
WorkflowStructs.SignatureData calldata sigRegister
87+
) external returns (address ipId, address ipRoyaltyVault);
88+
89+
/// @notice Distribute royalty tokens to the authors of the IP.
90+
/// @param ipId The ID of the IP.
91+
/// @param ipRoyaltyVault The address of the royalty vault.
92+
/// @param royaltyShares Authors of the IP and their shares of the royalty tokens, see {WorkflowStructs.RoyaltyShare}.
93+
/// @param sigApproveRoyaltyTokens The signature data for approving the royalty tokens.
94+
function distributeRoyaltyTokens(
95+
address ipId,
96+
address ipRoyaltyVault,
97+
WorkflowStructs.RoyaltyShare[] calldata royaltyShares,
98+
WorkflowStructs.SignatureData calldata sigApproveRoyaltyTokens
99+
) external;
100+
}

contracts/lib/Errors.sol

+13-1
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,21 @@ library Errors {
4444
////////////////////////////////////////////////////////////////////////////
4545
// Royalty Workflows //
4646
////////////////////////////////////////////////////////////////////////////
47-
/// @notice Zero address provided as a param to the GroupingWorkflows.
47+
/// @notice Zero address provided as a param to the RoyaltyWorkflows.
4848
error RoyaltyWorkflows__ZeroAddressParam();
4949

50+
////////////////////////////////////////////////////////////////////////////
51+
// Royalty Token Distribution Workflows //
52+
////////////////////////////////////////////////////////////////////////////
53+
/// @notice Zero address provided as a param to the RoyaltyTokenDistributionWorkflows.
54+
error RoyaltyTokenDistributionWorkflows__ZeroAddressParam();
55+
56+
/// @notice Total percentages exceed 100%.
57+
error RoyaltyTokenDistributionWorkflows__TotalPercentagesExceeds100Percent();
58+
59+
/// @notice Royalty vault not deployed.
60+
error RoyaltyTokenDistributionWorkflows__RoyaltyVaultNotDeployed();
61+
5062
////////////////////////////////////////////////////////////////////////////
5163
// SPGNFT //
5264
////////////////////////////////////////////////////////////////////////////

contracts/lib/LicensingHelper.sol

+98-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.26;
33

4+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
46
import { Errors as CoreErrors } from "@storyprotocol/core/lib/Errors.sol";
7+
import { ILicenseTemplate } from "@storyprotocol/core/interfaces/modules/licensing/ILicenseTemplate.sol";
58
import { ILicensingModule } from "@storyprotocol/core/interfaces/modules/licensing/ILicensingModule.sol";
69
import { IPILicenseTemplate, PILTerms } from "@storyprotocol/core/interfaces/modules/licensing/IPILicenseTemplate.sol";
710

811
/// @title Periphery Licensing Helper Library
912
/// @notice Library for all licensing related helper functions for Periphery contracts.
1013
library LicensingHelper {
11-
/// @dev Registers PIL License Terms and attaches them to the given IP.
14+
using SafeERC20 for IERC20;
15+
16+
/// @dev Registers multiple PIL License Terms and attaches them to the given IP.
1217
/// @param ipId The ID of the IP.
1318
/// @param pilTemplate The address of the PIL License Template.
1419
/// @param licensingModule The address of the Licensing Module.
@@ -24,11 +29,34 @@ library LicensingHelper {
2429
) internal returns (uint256[] memory licenseTermsIds) {
2530
licenseTermsIds = new uint256[](terms.length);
2631
for (uint256 i = 0; i < terms.length; i++) {
27-
licenseTermsIds[i] = IPILicenseTemplate(pilTemplate).registerLicenseTerms(terms[i]);
28-
attachLicenseTerms(ipId, licensingModule, licenseRegistry, pilTemplate, licenseTermsIds[i]);
32+
licenseTermsIds[i] = registerPILTermsAndAttach(
33+
ipId,
34+
pilTemplate,
35+
licensingModule,
36+
licenseRegistry,
37+
terms[i]
38+
);
2939
}
3040
}
3141

42+
/// @dev Registers a single PIL License and attaches it to the given IP.
43+
/// @param ipId The ID of the IP.
44+
/// @param pilTemplate The address of the PIL License Template.
45+
/// @param licensingModule The address of the Licensing Module.
46+
/// @param licenseRegistry The address of the License Registry.
47+
/// @param terms The PIL terms to be registered.
48+
/// @return licenseTermsId The ID of the registered PIL terms.
49+
function registerPILTermsAndAttach(
50+
address ipId,
51+
address pilTemplate,
52+
address licensingModule,
53+
address licenseRegistry,
54+
PILTerms calldata terms
55+
) internal returns (uint256 licenseTermsId) {
56+
licenseTermsId = IPILicenseTemplate(pilTemplate).registerLicenseTerms(terms);
57+
attachLicenseTerms(ipId, licensingModule, licenseRegistry, pilTemplate, licenseTermsId);
58+
}
59+
3260
/// @dev Attaches license terms to the given IP.
3361
/// @param ipId The ID of the IP.
3462
/// @param licensingModule The address of the Licensing Module.
@@ -53,4 +81,71 @@ library LicensingHelper {
5381
}
5482
}
5583
}
84+
85+
/// @dev Collect mint fees for all parent IPs from the payer and set approval for Royalty Module to spend mint fees.
86+
/// @param payerAddress The address of the payer for the license mint fees.
87+
/// @param royaltyModule The address of the Royalty Module.
88+
/// @param licensingModule The address of the Licensing Module.
89+
/// @param licenseTemplate The address of the license template.
90+
/// @param parentIpIds The IDs of all the parent IPs.
91+
/// @param licenseTermsIds The IDs of the license terms for each corresponding parent IP.
92+
function collectMintFeesAndSetApproval(
93+
address payerAddress,
94+
address royaltyModule,
95+
address licensingModule,
96+
address licenseTemplate,
97+
address[] memory parentIpIds,
98+
uint256[] memory licenseTermsIds
99+
) internal {
100+
ILicenseTemplate lct = ILicenseTemplate(licenseTemplate);
101+
(address royaltyPolicy, , , address mintFeeCurrencyToken) = lct.getRoyaltyPolicy(licenseTermsIds[0]);
102+
103+
if (royaltyPolicy != address(0)) {
104+
// Get total mint fee for all parent IPs
105+
uint256 totalMintFee = aggregateMintFees({
106+
payerAddress: payerAddress,
107+
licensingModule: licensingModule,
108+
licenseTemplate: licenseTemplate,
109+
parentIpIds: parentIpIds,
110+
licenseTermsIds: licenseTermsIds
111+
});
112+
113+
if (totalMintFee != 0) {
114+
// Transfer mint fee from payer to this contract
115+
IERC20(mintFeeCurrencyToken).safeTransferFrom(payerAddress, address(this), totalMintFee);
116+
117+
// Approve Royalty Policy to spend mint fee
118+
IERC20(mintFeeCurrencyToken).forceApprove(royaltyModule, totalMintFee);
119+
}
120+
}
121+
}
122+
123+
/// @dev Aggregate license mint fees for all parent IPs.
124+
/// @param payerAddress The address of the payer for the license mint fees.
125+
/// @param licensingModule The address of the Licensing Module.
126+
/// @param licenseTemplate The address of the license template.
127+
/// @param parentIpIds The IDs of all the parent IPs.
128+
/// @param licenseTermsIds The IDs of the license terms for each corresponding parent IP.
129+
/// @return totalMintFee The sum of license mint fees across all parent IPs.
130+
function aggregateMintFees(
131+
address payerAddress,
132+
address licensingModule,
133+
address licenseTemplate,
134+
address[] memory parentIpIds,
135+
uint256[] memory licenseTermsIds
136+
) internal view returns (uint256 totalMintFee) {
137+
uint256 mintFee;
138+
139+
for (uint256 i = 0; i < parentIpIds.length; i++) {
140+
(, mintFee) = ILicensingModule(licensingModule).predictMintingLicenseFee({
141+
licensorIpId: parentIpIds[i],
142+
licenseTemplate: licenseTemplate,
143+
licenseTermsId: licenseTermsIds[i],
144+
amount: 1,
145+
receiver: payerAddress,
146+
royaltyContext: ""
147+
});
148+
totalMintFee += mintFee;
149+
}
150+
}
56151
}

contracts/lib/WorkflowStructs.sol

+8
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,12 @@ library WorkflowStructs {
3838
uint256[] licenseTermsIds;
3939
bytes royaltyContext;
4040
}
41+
42+
/// @notice Struct for royalty shares information for royalty token distribution.
43+
/// @param author The address of the author.
44+
/// @param percentage The percentage of the royalty share, 100_000_000 represents 100%.
45+
struct RoyaltyShare {
46+
address author;
47+
uint32 percentage;
48+
}
4149
}

0 commit comments

Comments
 (0)