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: '0xba8E30d9EB784Badc2aF610F56d99d212BC2A90c',
    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',
    },
    txOptions: { waitForTransaction: true },
})
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.

There is no Story SDK method for this, so you’ll have to use viem’s writeContract method.

import { totalLicenseTokenLimitHook } from './abi/totalLicenseTokenLimitHook'

const { request } = await publicClient.simulateContract({
    // address of TotalLicenseTokenLimitHook
    // from https://docs.story.foundation/developers/deployed-smart-contracts
    address: '0xba8E30d9EB784Badc2aF610F56d99d212BC2A90c',
    abi: totalLicenseTokenLimitHook,
    functionName: 'setTotalLicenseTokenLimit',
    args: [
        response.ipId, // ipId from the step above
        '0x2E896b0b2Fdb7457499B56AAaA4AE55BCB4Cd316', // the address of PILicenseTemplate from https://docs.story.foundation/developers/deployed-smart-contracts
        response.licenseTermsIds![0], // licenseTermsId
        1n, // limit (as BigInt)
    ],
    account: account, // Specify the account to use for permission checking
})

// Prepare transaction
const hash = await walletClient.writeContract({ ...request, account: account })

// Wait for transaction to be mined
const receipt = await publicClient.waitForTransactionReceipt({
    hash,
})

Commercializer Checker Hooks

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