Hooks allow developers to create custom implementations, restrictions, and functionality upon minting License Tokens or registering derivatives. There are two types of hooks:
  1. Licensing Hooks: allow you to add custom logic before minting license tokens (and registering derivatives). For example, requesting a dynamic price, limiting the amount of license tokens that can be minted, whitelists, etc. Licensing Hooks can be added / modified on a licensing config at any point.
  2. Commercializer Checker Hooks: similar to Licensing Hooks, however they are directly a part of the license terms and do not change. You also cannot return a custom minting fee.

Licensing Hooks

These are contracts that implement the ILicensingHook interface, which extends from IModule. Most importantly, a Licensing Hook implements a beforeMintLicenseTokens function, which is a function that is called before a License Token is minted to implement custom logic and determine the final totalMintingFee of that License Token.
View the ILicensingHook smart contract here.
ILicensingHook.sol
/// @notice This function is called when the LicensingModule mints license tokens.
/// @dev The hook can be used to implement various checks and determine the minting price.
/// The hook should revert if the minting is not allowed.
/// @param caller The address of the caller who calling the mintLicenseTokens() function.
/// @param licensorIpId The ID of licensor IP from which issue the license tokens.
/// @param licenseTemplate The address of the license template.
/// @param licenseTermsId The ID of the license terms within the license template,
/// which is used to mint license tokens.
/// @param amount The amount of license tokens to mint.
/// @param receiver The address of the receiver who receive the license tokens.
/// @param hookData The data to be used by the licensing hook.
/// @return totalMintingFee The total minting fee to be paid when minting amount of license tokens.
function beforeMintLicenseTokens(
  address caller,
  address licensorIpId,
  address licenseTemplate,
  uint256 licenseTermsId,
  uint256 amount,
  address receiver,
  bytes calldata hookData
) external returns (uint256 totalMintingFee);
Note that it returns the totalMintingFee. You may be wondering, “I can set the minting fee in the License Terms, in the LicenseConfig, and return a dynamic price from beforeMintLicenseTokens. What will the final minting fee actually be?” Here is the priority:
Minting FeeImportance
The totalMintingFee returned from beforeMintLicenseTokensHighest Priority
The mintingFee set in the LicenseConfig⬇️
The mintingFee set in the License TermsLowest Priority
Beware of potentially malicious implementations of external license hooks. Please first verify the code of the hook you choose because it may be not reviewed or audited by the Story team.

Available Hooks

Below are available hooks deployed on our protocol that you can use.
View the deployed addresses for these hooks here.
HookDescriptionContract Code
LockLicenseHookStop the minting of license tokens or registering new derivatives.View here ↗️
TotalLicenseTokenLimitHookSet a limit on the amount of license tokens that can be minted, updatable at any time.View here ↗️

Implementing the Hooks

Licensing Hooks are ultimately a smart contract that implements the ILicensingHook interface. You can view the interface here. We have a few Licensing Hooks deployed already (view the chart above). In order to actually use a Licensing Hook, you must set it in the Licensing Config, which is basically a set of configurations that you set on License Terms when attaching terms to an IP Asset.
1

Create Licensing Config

First you have to create a Licensing Config:
import { LicensingConfig } from '@story-protocol/core-sdk';

const licensingConfig: LicensingConfig = {
    isSet: true,
    mintingFee: 0n,
    // address of TotalLicenseTokenLimitHook
    // from https://docs.story.foundation/developers/deployed-smart-contracts
    licensingHook: '0xaBAD364Bfa41230272b08f171E0Ca939bD600478',
    hookData: zeroAddress,
    commercialRevShare: 0,
    disabled: false,
    expectMinimumGroupRewardShare: 0,
    expectGroupRewardPool: zeroAddress,
}
2

Set the Licensing Config

Next, we’ll set the Licensing Config on the License Terms. In the following example, we’ll show this happening upon registering the IP Asset:
This code snippet requires a bit of setup, and it meant for developers who already understand how to setup the TypeScript SDK. If you want to learn more, check out the working code example.
This uses the mintAndRegisterIpAssetWithPilTerms method found here.
const response = await client.ipAsset.mintAndRegisterIpAssetWithPilTerms({
    spgNftContract: '0xc32A8a0FF3beDDDa58393d022aF433e78739FAbc', // public spg contract for ease-of-use
    licenseTermsData: [
        {
            terms: { defaultMintingFee: 0, commercialUse: true, ... }, // dummy license terms
            // set the licensing config here
            licensingConfig: licensingConfig
        },
    ],
    ipMetadata: {
        ipMetadataURI: 'test-uri',
        ipMetadataHash: toHex('test-metadata-hash', { size: 32 }),
        nftMetadataHash: toHex('test-nft-metadata-hash', { size: 32 }),
        nftMetadataURI: 'test-nft-uri',
    }
})

console.log(`Token ID: ${response.tokenId}, IPA ID: ${response.ipId}, License Terms ID: ${response.licenseTermsIds}`);
3

Set the Limit to 1

Now that we have set the Licensing Config on our terms, we can call the setTotalLicenseTokenLimit function on the hook and set the max # of licenses that can be minted to 1.
const hookResponse = await client.license.setMaxLicenseTokens({
  ipId: response.ipId,
  licenseTermsId: response.licenseTermsIds![0],
  maxLicenseTokens: 1000,
});

console.log(`Max license tokens set at transaction hash ${hookResponse.txHash}`);

Creating a New Licensing Hook

Please follow the below process for creating a new licensing hook and getting it whitelisted into Story’s protocol.
  1. Develop your Hook: You may fork this template repository to bootstrap your development. It includes an example hook with tests against our Aeneid protocol, but using it is optional.
    See license-caller-whitelist-hook as an example that was used to develop the LicenseCallerWhitelistHook.sol hook.
  2. Fork Registered Modules Repository: Fork the registered-modules repository to your own GitHub account.
  3. Update the Module List: In your registered-modules repository, add your hook’s details to the hook-modules.json file. Make sure to deploy & verify on block explorer your hook on both aeneid and mainnet. Ensure that you adhere to the following JSON structure:
    {
      "name": "YourModuleName",
      "aeneid": {
        "address": "YourModuleAddress",
        "blockExplorerLink": "YourModuleBlockExplorerLink"
      },
      "mainnet": {
        "address": "YourModuleAddress",
        "blockExplorerLink": "YourModuleBlockExplorerLink"
      }
    }
    
    Replace YourModuleName, YourModuleAddress, and YourModuleBlockExplorerLink with your hook’s name, its address, and the link to its block explorer page, respectively. Example:
    {
      "name": "LicenseCallerWhitelistHook",
      "aeneid": {
        "address": "0x37be56d9fb06d885cda3cb010096c94c28b4d658",
        "blockExplorerLink": "https://aeneid.storyscan.io/address/0x37be56d9fb06d885cda3cb010096c94c28b4d658?tab=contract"
      },
      "mainnet": {
        "address": "0x6d9d51a444c8318e8840e75dab7ed81b5a714610",
        "blockExplorerLink": "https://www.storyscan.io/address/0x6d9d51a444c8318e8840e75dab7ed81b5a714610?tab=contract"
      }
    }
    
  4. Create a Pull Request (PR): Once you have added your hook, create a pull request against this repository. In your PR’s description, add the following information (replace the values with your own):
    ## Register my module
    
    - Module type: `hook`
    - Module name: `LicenseCallerWhitelistHook`
    - Aeneid Module address: `0x37be56d9fb06d885cda3cb010096c94c28b4d658`
    - Mainnet Module address: `0x6d9d51a444c8318e8840e75dab7ed81b5a714610`
    - My module is immutable: yes
    - My module is using an upgradeable proxy: no
    - My module has been verified on the block explorer (required): yes
    - Summary of my module: Hook for allowing a licensor to gate which addresses can mint a license. The licensor can add/remove an address at any time.
    - GitHub repository with source code and tests: https://github.com/jacob-tucker/license-caller-whitelist-hook
    
  5. Await Verification: After your PR is submitted, it will be reviewed. Once a security audit has been performed and completed, and the module whitelisted into the protocol, the PR will be merged. At this point your module will be officially registered and recognized as safe for use within the Story community.
We look forward to seeing your contributions and expanding the Story module ecosystem!

Commercializer Checker Hooks

Documentation coming soon. If you have questions in the meantime, ask in the Builder’s Discord.