Skip to content

Commit 5fdbc74

Browse files
committed
fix(register-derivatives): collect and approve mint fees for commercial licenses
1 parent 0308d83 commit 5fdbc74

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

contracts/SPGNFT.sol

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity ^0.8.23;
33

44
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
5+
// solhint-disable-next-line max-line-length
56
import { ERC721URIStorageUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
67
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
78
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

contracts/StoryProtocolGateway.sol

+141
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ import { IAccessController } from "@storyprotocol/core/interfaces/access/IAccess
1313
// solhint-disable-next-line max-line-length
1414
import { IPILicenseTemplate, PILTerms } from "@storyprotocol/core/interfaces/modules/licensing/IPILicenseTemplate.sol";
1515
import { ILicensingModule } from "@storyprotocol/core/interfaces/modules/licensing/ILicensingModule.sol";
16+
import { ILicenseTemplate } from "@storyprotocol/core/interfaces/modules/licensing/ILicenseTemplate.sol";
17+
import { ILicenseRegistry } from "@storyprotocol/core/interfaces/registries/ILicenseRegistry.sol";
18+
import { ILicensingHook } from "@storyprotocol/core/interfaces/modules/licensing/ILicensingHook.sol";
19+
import { Licensing } from "@storyprotocol/core/lib/Licensing.sol";
20+
import { IRoyaltyModule } from "@storyprotocol/core/interfaces/modules/royalty/IRoyaltyModule.sol";
21+
1622
import { ICoreMetadataModule } from "@storyprotocol/core/interfaces/modules/metadata/ICoreMetadataModule.sol";
1723
import { IIPAssetRegistry } from "@storyprotocol/core/interfaces/registries/IIPAssetRegistry.sol";
1824
import { AccessPermission } from "@storyprotocol/core/lib/AccessPermission.sol";
25+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
26+
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
1927

2028
import { IStoryProtocolGateway } from "./interfaces/IStoryProtocolGateway.sol";
2129
import { ISPGNFT } from "./interfaces/ISPGNFT.sol";
@@ -24,6 +32,7 @@ import { SPGNFTLib } from "./lib/SPGNFTLib.sol";
2432

2533
contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable, UUPSUpgradeable {
2634
using ERC165Checker for address;
35+
using SafeERC20 for IERC20;
2736

2837
/// @dev Storage structure for the SPG
2938
/// @param nftContractBeacon The address of the NFT contract beacon.
@@ -44,6 +53,12 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
4453
/// @notice The address of the Licensing Module.
4554
ILicensingModule public immutable LICENSING_MODULE;
4655

56+
/// @notice The address of the License Registry.
57+
ILicenseRegistry public immutable LICENSE_REGISTRY;
58+
59+
/// @notice The address of the Royalty Module.
60+
IRoyaltyModule public immutable ROYALTY_MODULE;
61+
4762
/// @notice The address of the Core Metadata Module.
4863
ICoreMetadataModule public immutable CORE_METADATA_MODULE;
4964

@@ -65,6 +80,8 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
6580
address accessController,
6681
address ipAssetRegistry,
6782
address licensingModule,
83+
address licenseRegistry,
84+
address royaltyModule,
6885
address coreMetadataModule,
6986
address pilTemplate,
7087
address licenseToken
@@ -73,13 +90,17 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
7390
accessController == address(0) ||
7491
ipAssetRegistry == address(0) ||
7592
licensingModule == address(0) ||
93+
licenseRegistry == address(0) ||
94+
royaltyModule == address(0) ||
7695
coreMetadataModule == address(0) ||
7796
licenseToken == address(0)
7897
) revert Errors.SPG__ZeroAddressParam();
7998

8099
ACCESS_CONTROLLER = IAccessController(accessController);
81100
IP_ASSET_REGISTRY = IIPAssetRegistry(ipAssetRegistry);
82101
LICENSING_MODULE = ILicensingModule(licensingModule);
102+
LICENSE_REGISTRY = ILicenseRegistry(licenseRegistry);
103+
ROYALTY_MODULE = IRoyaltyModule(royaltyModule);
83104
CORE_METADATA_MODULE = ICoreMetadataModule(coreMetadataModule);
84105
PIL_TEMPLATE = IPILicenseTemplate(pilTemplate);
85106
LICENSE_TOKEN = ILicenseToken(licenseToken);
@@ -254,6 +275,14 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
254275
ipId = IP_ASSET_REGISTRY.register(block.chainid, nftContract, tokenId);
255276
_setMetadata(ipMetadata, ipId);
256277

278+
_collectMintFeesAndSetApproval(
279+
msg.sender,
280+
ipId,
281+
derivData.parentIpIds,
282+
derivData.licenseTemplate,
283+
derivData.licenseTermsIds
284+
);
285+
257286
LICENSING_MODULE.registerDerivative({
258287
childIpId: ipId,
259288
parentIpIds: derivData.parentIpIds,
@@ -289,6 +318,15 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
289318
address(LICENSING_MODULE),
290319
ILicensingModule.registerDerivative.selector
291320
);
321+
322+
_collectMintFeesAndSetApproval(
323+
msg.sender,
324+
ipId,
325+
derivData.parentIpIds,
326+
derivData.licenseTemplate,
327+
derivData.licenseTermsIds
328+
);
329+
292330
LICENSING_MODULE.registerDerivative({
293331
childIpId: ipId,
294332
parentIpIds: derivData.parentIpIds,
@@ -441,6 +479,109 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
441479
_setMetadata(ipMetadata, ipId);
442480
}
443481

482+
/// @dev Collect mint fees for all parent IPs from the payer and set approval for Royalty Module to spend mint fees.
483+
/// @param payerAddress The address of the payer for the license mint fees.
484+
/// @param childIpId The ID of the derivative IP.
485+
/// @param parentIpIds The IDs of all the parent IPs.
486+
/// @param licenseTemplate The address of the license template.
487+
/// @param licenseTermsIds The IDs of the license terms for each corresponding parent IP.
488+
function _collectMintFeesAndSetApproval(
489+
address payerAddress,
490+
address childIpId,
491+
address[] calldata parentIpIds,
492+
address licenseTemplate,
493+
uint256[] calldata licenseTermsIds
494+
) private {
495+
// Get currency token and royalty policy, assumes all parent IPs have the same currency token.
496+
ILicenseTemplate lct = ILicenseTemplate(licenseTemplate);
497+
(address royaltyPolicy, , , address mintFeeCurrencyToken) = lct.getRoyaltyPolicy(licenseTermsIds[0]);
498+
499+
if (royaltyPolicy != address(0)) {
500+
// Get total mint fee for all parent IPs
501+
uint256 totalMintFee = _aggregateMintFees(parentIpIds, childIpId, licenseTemplate, licenseTermsIds);
502+
503+
if (totalMintFee != 0) {
504+
// Transfer mint fee from payer to this contract
505+
IERC20(mintFeeCurrencyToken).safeTransferFrom(payerAddress, address(this), totalMintFee);
506+
507+
// Approve Royalty Policy to spend mint fee
508+
IERC20(mintFeeCurrencyToken).forceApprove(royaltyPolicy, totalMintFee);
509+
}
510+
}
511+
}
512+
513+
/// @dev Aggregate license mint fees for all parent IPs.
514+
/// @param parentIpIds The IDs of all the parent IPs.
515+
/// @param childIpId The ID of the derivative IP.
516+
/// @param licenseTemplate The address of the license template.
517+
/// @param licenseTermsIds The IDs of the license terms for each corresponding parent IP.
518+
/// @return totalMintFee The sum of license mint fees across all parent IPs.
519+
function _aggregateMintFees(
520+
address[] calldata parentIpIds,
521+
address childIpId,
522+
address licenseTemplate,
523+
uint256[] calldata licenseTermsIds
524+
) private returns (uint256 totalMintFee) {
525+
totalMintFee = 0;
526+
527+
for (uint256 i = 0; i < parentIpIds.length; i++) {
528+
totalMintFee += _getMintFeeForSingleParent(
529+
childIpId,
530+
parentIpIds[i],
531+
licenseTemplate,
532+
licenseTermsIds[i],
533+
1
534+
);
535+
}
536+
}
537+
538+
/// @dev Fetch the license token mint fee from the licensing hook or license terms for the given parent IP.
539+
/// @param childIpId The ID of the derivative IP.
540+
/// @param parentIpId The ID of the parent IP.
541+
/// @param licenseTemplate The address of the license template.
542+
/// @param licenseTermsId The ID of the license terms for the parent IP.
543+
/// @param amount The amount of licenses to mint.
544+
function _getMintFeeForSingleParent(
545+
address childIpId,
546+
address parentIpId,
547+
address licenseTemplate,
548+
uint256 licenseTermsId,
549+
uint256 amount
550+
) private returns (uint256) {
551+
ILicenseTemplate lct = ILicenseTemplate(licenseTemplate);
552+
553+
// Get mint fee set by license terms
554+
(address royaltyPolicy, , uint256 mintFeeSetByLicenseTerms, ) = lct.getRoyaltyPolicy(licenseTermsId);
555+
556+
// If no royalty policy, return 0
557+
if (royaltyPolicy == address(0)) return 0;
558+
559+
uint256 mintFeeSetByHook = 0;
560+
561+
Licensing.LicensingConfig memory licensingConfig = LICENSE_REGISTRY.getLicensingConfig(
562+
parentIpId,
563+
licenseTemplate,
564+
licenseTermsId
565+
);
566+
567+
// Get mint fee from licensing hook
568+
if (licensingConfig.licensingHook != address(0)) {
569+
mintFeeSetByHook = ILicensingHook(licensingConfig.licensingHook).beforeRegisterDerivative(
570+
address(this),
571+
childIpId,
572+
parentIpId,
573+
licenseTemplate,
574+
licenseTermsId,
575+
licensingConfig.hookData
576+
);
577+
}
578+
579+
if (!licensingConfig.isSet) return mintFeeSetByLicenseTerms * amount;
580+
if (licensingConfig.licensingHook == address(0)) return licensingConfig.mintingFee * amount;
581+
582+
return mintFeeSetByHook;
583+
}
584+
444585
//
445586
// Upgrade
446587
//

0 commit comments

Comments
 (0)