smart-contracts
Skill4 starsChanged 9 months agoMITView on GitHub
Story Protocol Solidity smart contract interaction. Use when user mentions "Story contract", "IPAssetRegistry", "Solidity", "Story smart contract", "foundry", "forge", "LicensingModule", "RoyaltyModule", or wants to interact with Story contracts directly.
3 other projects use a version of this.
Adopted by
Most recently taken up by
- rylsherdamz-rgb/stellar-forgetheir own version, first seen Nov 8, 2026
- DongDuong2001/pudo-code-systemtheir own version, first seen Oct 13, 2026
- poorvith-mp/skills-developertheir own version, first seen Aug 19, 2026
Other versions3 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 3
- rylsherdamz-rgb/stellar-forge★ 17same name
- DongDuong2001/pudo-code-system★ 5same name
- poorvith-mp/skills-developer★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 3 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 382
All SPG workflow contracts use shared structs defined in WorkflowStructs:
- Distinctive: line 430
All Foundry tests should fork the Aeneid testnet for integration testing:
- Distinctive: line 448
Use the official boilerplate for quick start: https://github.com/storyprotocol/story-protocol-boilerplate
- Very specific: line 537
Only the IP Account owner (NFT holder) can call setPermission.
- Very specific: line 583
Solution: Always use the SPG contract's built-in multicall() function.
- Very specific: line 587
Multicall3 with SPGNFT: Never use the standard Multicall3 for SPG operations that involve SPGNFT minting.
- Very specific: line 589
msg.sender in delegatecall: When using delegatecall, msg.sender refers to the contract executing the delegatecall, not the original EOA.
- Very specific: line 597
ERC-20 approvals for royalties: Before calling payRoyaltyOnBehalf, the caller must approve the RoyaltyModule to spend the payment token.
- Very specific: line 599
Permission before module calls: If a third-party contract calls a module on behalf of an IP, the IP owner must first grant permission via …
- Distinctive: line 601
SignatureData for SPG with existing NFTs: When using SPG to register an existing NFT, you must provide EIP-712 signatures (SignatureData) …
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Story Protocol Smart Contract Interaction
Guide for interacting with Story Protocol smart contracts directly in Solidity, including core contracts, SPG workflows, Foundry testing, and permission management.
Architecture Overview
Story Protocol contracts are organized into two layers:
| Layer | Purpose | Examples |
| Core | Individual protocol operations, registries, and modules | IPAssetRegistry, LicensingModule, RoyaltyModule |
| Periphery (SPG) | Batched workflow contracts combining multiple core calls | RegistrationWorkflows, DerivativeWorkflows |
Registries store state (IP assets, licenses, modules). Modules execute logic (licensing, royalties, disputes). The AccessController gates which addresses can call which module functions on behalf of an IP Account.
┌─────────────────────────────────────┐
│ SPG Workflow Contracts │
│ (RegistrationWorkflows, Derivative │
│ Workflows, LicenseAttachment...) │
└──────────────┬──────────────────────┘
│ calls
┌──────────────▼──────────────────────┐
│ Core Contracts │
│ ┌──────────────┐ ┌───────────────┐ │
│ │ Registries │ │ Modules │ │
│ │ IPAsset │ │ Licensing │ │
│ │ License │ │ Royalty │ │
│ │ Module │ │ Dispute │ │
│ └──────────────┘ │ Access │ │
│ │ CoreMetadata │ │
│ │ Grouping │ │
│ └───────────────┘ │
└─────────────────────────────────────┘Core Contracts
IPAssetRegistry
Global registry of all IP Assets. Each registered NFT gets an ERC-6551 IP Account (the ipId).
Address: 0x77319B4031e6eF1250907aa00018B8B1c67a244b
import { IPAssetRegistry } from "@story-protocol/core/registries/IPAssetRegistry.sol";
// Register an existing ERC-721 NFT as an IP Asset
function register(uint256 chainid, address tokenContract, uint256 tokenId)
external returns (address ipId);
// Derive the ipId deterministically (no tx needed)
function ipId(uint256 chainid, address tokenContract, uint256 tokenId)
external view returns (address);
// Check if an NFT is already registered
function isRegistered(address id) external view returns (bool);Key events:
event IPRegistered(
address ipId,
uint256 chainId,
address tokenContract,
uint256 tokenId,
string name,
string uri,
uint256 registrationDate
);Usage pattern:
IPAssetRegistry public immutable IP_ASSET_REGISTRY =
IPAssetRegistry(0x77319B4031e6eF1250907aa00018B8B1c67a244b);
// Register an NFT you own
address ipId = IP_ASSET_REGISTRY.register(block.chainid, address(myNft), tokenId);
// Check ipId for any NFT
address existingIpId = IP_ASSET_REGISTRY.ipId(block.chainid, address(myNft), tokenId);
require(IP_ASSET_REGISTRY.isRegistered(existingIpId), "Not registered");LicensingModule
Manages license terms attachment and license token minting.
Address: 0x04fbd8a2e56dd85CFD5500A4A4DfA955B9f1dE6f
import { LicensingModule } from "@story-protocol/core/modules/licensing/LicensingModule.sol";
// Attach license terms to an IP (IP owner or authorized caller)
function attachLicenseTerms(address ipId, address licenseTemplate, uint256 licenseTermsId)
external;
// Mint license tokens for a given IP + terms pair
function mintLicenseTokens(
address licensorIpId,
address licenseTemplate,
uint256 licenseTermsId,
uint256 amount,
address receiver,
bytes calldata royaltyContext
) external returns (uint256 startLicenseTokenId);Key events:
event LicenseTermsAttached(address caller, address ipId, address licenseTemplate, uint256 licenseTermsId);
event LicenseTokensMinted(address caller, address licensorIpId, address licenseTemplate, uint256 licenseTermsId, uint256 amount, address receiver, uint256 startLicenseTokenId);Usage pattern:
LicensingModule public immutable LICENSING_MODULE =
LicensingModule(0x04fbd8a2e56dd85CFD5500A4A4DfA955B9f1dE6f);
address PIL_TEMPLATE = 0x2E896b0b2Fdb7457499B56AAaA4AE55BCB4Cd316;
// Attach PIL terms (ID obtained from PILicenseTemplate.registerLicenseTerms())
LICENSING_MODULE.attachLicenseTerms(ipId, PIL_TEMPLATE, licenseTermsId);
// Mint 1 license token to receiver
uint256 startId = LICENSING_MODULE.mintLicenseTokens(
ipId, PIL_TEMPLATE, licenseTermsId, 1, receiver, ""
);RoyaltyModule
Handles royalty payments between IP Assets.
Address: 0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086
import { RoyaltyModule } from "@story-protocol/core/modules/royalty/RoyaltyModule.sol";
// Pay royalties on behalf of an IP to its parent
function payRoyaltyOnBehalf(
address receiverIpId,
address payerIpId,
address token,
uint256 amount
) external;Key events:
event RoyaltyPaid(address receiverIpId, address payerIpId, address sender, address token, uint256 amount);Usage pattern:
RoyaltyModule public immutable ROYALTY_MODULE =
RoyaltyModule(0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086);
address WIP = 0x1514000000000000000000000000000000000000;
// Approve the RoyaltyModule to spend tokens first
IERC20(WIP).approve(address(ROYALTY_MODULE), amount);
// Pay royalties
ROYALTY_MODULE.payRoyaltyOnBehalf(parentIpId, childIpId, WIP, amount);DisputeModule
Allows raising and resolving disputes against IP Assets.
Address: 0x9b7A9c70AFF961C799110954fc06F3093aeb94C5
import { DisputeModule } from "@story-protocol/core/modules/dispute/DisputeModule.sol";
// Raise a dispute against an IP
function raiseDispute(
address targetIpId,
string calldata linkToDisputeEvidence,
bytes32 targetTag,
bytes calldata data
) external returns (uint256 disputeId);
// Resolve a dispute (arbiter only)
function resolveDispute(uint256 disputeId, bytes calldata data) external;Key events:
event DisputeRaised(uint256 disputeId, address targetIpId, address disputeInitiator, bytes32 arbitrationPolicy, bytes32 linkToDisputeEvidence, bytes32 targetTag, bytes data);
event DisputeResolved(uint256 disputeId);AccessController
Manages permissions for which addresses can call module functions on behalf of IP Accounts.
Address: 0xcCF37d0a503Ee1D4C11208672e622ed3DFB2275a
import { AccessController } from "@story-protocol/core/access/AccessController.sol";
// Set a permission
function setPermission(
address ipAccount,
address signer,
address to,
bytes4 func,
uint8 permission // 0=ABSTAIN, 1=ALLOW, 2=DENY
) external;
// Check a permission
function checkPermission(
address ipAccount,
address signer,
address to,
bytes4 func
) external view;See the AccessController Pattern section below for detailed permission management.
CoreMetadataModule
Sets IP metadata onchain.
Address: 0x6E81a25C99C6e8430aeC7353325EB138aFE5DC16
import { CoreMetadataModule } from "@story-protocol/core/modules/metadata/CoreMetadataModule.sol";
// Set all metadata fields
function setAll(
address ipId,
string calldata metadataURI,
bytes32 metadataHash,
bytes32 nftMetadataHash
) external;
// Update just the metadata hash
function updateMetadataHash(address ipId, bytes32 metadataHash) external;
// Freeze metadata (irreversible)
function freezeMetadata(address ipId) external;SPG Workflow Contracts
SPG (Story Protocol Gateway) workflow contracts batch multiple core operations into single transactions for gas efficiency and better UX.
RegistrationWorkflows
Address: 0xbe39E1C756e921BD25DF86e7AAa31106d1eb0424
import { RegistrationWorkflows } from "@story-protocol/periphery/workflows/RegistrationWorkflows.sol";
import { WorkflowStructs } from "@story-protocol/periphery/lib/WorkflowStructs.sol";
// Mint an NFT from SPGNFT collection + register as IP in one tx
function mintAndRegisterIp(
address spgNftContract,
address recipient,
WorkflowStructs.IPMetadata calldata ipMetadata,
bool allowDuplicates
) external returns (address ipId, uint256 tokenId);
// Register an existing NFT as IP
function registerIp(
address nftContract,
uint256 tokenId,
WorkflowStructs.IPMetadata calldata ipMetadata,
WorkflowStructs.SignatureData calldata sigMetadata,
WorkflowStructs.SignatureData calldata sigRegister
) external returns (address ipId);DerivativeWorkflows
Address: 0x9e2d496f72C547C2C535B167e06ED8729B374a4f
import { DerivativeWorkflows } from "@story-protocol/periphery/workflows/DerivativeWorkflows.sol";
// Mint + register + make derivative in one tx
function mintAndRegisterIpAndMakeDerivative(
address spgNftContract,
WorkflowStructs.MakeDerivative calldata derivData,
WorkflowStructs.IPMetadata calldata ipMetadata,
address recipient,
bool allowDuplicates
) external returns (address ipId, uint256 tokenId);
// Register existing NFT + make derivative
function registerIpAndMakeDerivative(
address nftContract,
uint256 tokenId,
WorkflowStructs.MakeDerivative calldata derivData,
WorkflowStructs.IPMetadata calldata ipMetadata,
WorkflowStructs.SignatureData calldata sigMetadata,
WorkflowStructs.SignatureData calldata sigRegister
) external returns (address ipId);LicenseAttachmentWorkflows
Address: 0xcC2E862bCee5B6036Db0de6E06Ae87e524a79fd8
import { LicenseAttachmentWorkflows } from "@story-protocol/periphery/workflows/LicenseAttachmentWorkflows.sol";
// Mint + register + attach PIL terms in one tx
function mintAndRegisterIpAndAttachPILTerms(
address spgNftContract,
address recipient,
WorkflowStructs.IPMetadata calldata ipMetadata,
PILTerms[] calldata terms,
bool allowDuplicates
) external returns (address ipId, uint256 tokenId, uint256[] memory licenseTermsIds);
// Register existing NFT + attach terms
function registerIpAndAttachPILTerms(
address nftContract,
uint256 tokenId,
WorkflowStructs.IPMetadata calldata ipMetadata,
PILTerms[] calldata terms,
WorkflowStructs.SignatureData calldata sigMetadata,
WorkflowStructs.SignatureData calldata sigRegister,
WorkflowStructs.SignatureData calldata sigAttach
) external returns (address ipId, uint256[] memory licenseTermsIds);GroupingWorkflows
Address: 0xD7c0beb3aa4DCD4723465f1ecAd045676c24CDCd
Batch operations for creating and managing IP groups:
mintAndRegisterIpAndAttachLicenseAndAddToGroup-- Mint + register + attach license + add to groupregisterIpAndAttachLicenseAndAddToGroup-- Register existing NFT + attach license + add to groupregisterGroupAndAttachLicenseAndAddIps-- Create group + attach license + add multiple IPs
RoyaltyWorkflows
Address: 0x9515faE61E0c0447C6AC6dEe5628A2097aFE1890
Batch operations for royalty management:
claimAllRevenue-- Claim all accumulated revenue for an IP across multiple tokensclaimAllRevenueForAncestorIp-- Claim revenue that has flowed from descendants to ancestor IPs
RoyaltyTokenDistributionWorkflows
Address: 0xa38f42B8d33809917f23997B8423054aAB97322C
Distribute royalty vault tokens to multiple recipients during registration:
mintAndRegisterIpAndAttachPILTermsAndDistributeRoyaltyTokensmintAndRegisterIpAndMakeDerivativeAndDistributeRoyaltyTokens
WorkflowStructs
All SPG workflow contracts use shared structs defined in WorkflowStructs:
import { WorkflowStructs } from "@story-protocol/periphery/lib/WorkflowStructs.sol";
struct IPMetadata {
string ipMetadataURI; // IPFS URI for IP metadata JSON
bytes32 ipMetadataHash; // SHA-256 hash of IP metadata
string nftMetadataURI; // IPFS URI for NFT metadata JSON
bytes32 nftMetadataHash; // SHA-256 hash of NFT metadata
}
struct MakeDerivative {
address[] parentIpIds; // Parent IP addresses
uint256[] licenseTermsIds; // License terms to use
address licenseTemplate; // PILicenseTemplate address
bytes royaltyContext; // Usually empty ("")
uint256 maxMintingFee; // Max fee willing to pay (0 for unlimited)
uint256 maxRts; // Max royalty tokens (0 for unlimited)
uint256 maxRevenueShare; // Max revenue share (0 for unlimited)
}
struct SignatureData {
address signer; // Address that signed
uint256 deadline; // Expiration timestamp
bytes signature; // EIP-712 signature bytes
}Contract Addresses
See references/contract-addresses.md for the complete list. Key addresses (same on both networks unless noted):
| Contract | Address |
| IPAssetRegistry | 0x77319B4031e6eF1250907aa00018B8B1c67a244b |
| LicensingModule | 0x04fbd8a2e56dd85CFD5500A4A4DfA955B9f1dE6f |
| RoyaltyModule | 0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086 |
| DisputeModule | 0x9b7A9c70AFF961C799110954fc06F3093aeb94C5 |
| PILicenseTemplate | 0x2E896b0b2Fdb7457499B56AAaA4AE55BCB4Cd316 |
| AccessController | 0xcCF37d0a503Ee1D4C11208672e622ed3DFB2275a |
| RegistrationWorkflows | 0xbe39E1C756e921BD25DF86e7AAa31106d1eb0424 |
| DerivativeWorkflows | 0x9e2d496f72C547C2C535B167e06ED8729B374a4f |
Foundry Testing
Fork Testing Against Aeneid
All Foundry tests should fork the Aeneid testnet for integration testing:
# Run all tests
forge test --fork-url https://aeneid.storyrpc.io/
# Run a specific test file
forge test --fork-url https://aeneid.storyrpc.io/ --match-path test/1_LicenseTerms.t.sol
# Run with verbosity for debugging
forge test --fork-url https://aeneid.storyrpc.io/ -vvvv
# Run a specific test function
forge test --fork-url https://aeneid.storyrpc.io/ --match-test test_registerIpBoilerplate Repository
Use the official boilerplate for quick start: https://github.com/storyprotocol/story-protocol-boilerplate
Test Setup Pattern
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "forge-std/Test.sol";
import { IPAssetRegistry } from "@story-protocol/core/registries/IPAssetRegistry.sol";
import { LicensingModule } from "@story-protocol/core/modules/licensing/LicensingModule.sol";
import { PILicenseTemplate } from "@story-protocol/core/modules/licensing/PILicenseTemplate.sol";
import { RoyaltyModule } from "@story-protocol/core/modules/royalty/RoyaltyModule.sol";
contract StoryProtocolTest is Test {
IPAssetRegistry public constant IP_ASSET_REGISTRY =
IPAssetRegistry(0x77319B4031e6eF1250907aa00018B8B1c67a244b);
LicensingModule public constant LICENSING_MODULE =
LicensingModule(0x04fbd8a2e56dd85CFD5500A4A4DfA955B9f1dE6f);
PILicenseTemplate public constant PIL_TEMPLATE =
PILicenseTemplate(0x2E896b0b2Fdb7457499B56AAaA4AE55BCB4Cd316);
RoyaltyModule public constant ROYALTY_MODULE =
RoyaltyModule(0xD2f60c40fEbccf6311f8B47c4f2Ec6b040400086);
address public alice;
address public bob;
function setUp() public {
alice = makeAddr("alice");
bob = makeAddr("bob");
vm.deal(alice, 100 ether);
vm.deal(bob, 100 ether);
}
}AccessController Pattern
The AccessController manages a 3D permission table: (ipAccount, signer, target) -> permission.
Permission Values
| Value | Constant | Meaning |
| 0 | ABSTAIN | No explicit permission (falls through to global/default) |
| 1 | ALLOW | Explicitly allowed |
| 2 | DENY | Explicitly denied |
Wildcard Selectors
| Wildcard | Meaning |
address(0) for to | Applies to ALL modules/targets |
bytes4(0) for func | Applies to ALL function selectors |
Setting Permissions
AccessController public constant ACCESS_CONTROLLER =
AccessController(0xcCF37d0a503Ee1D4C11208672e622ed3DFB2275a);
// Allow 'operator' to call attachLicenseTerms on LicensingModule for this IP
ACCESS_CONTROLLER.setPermission(
ipId, // IP Account
operator, // Signer (who is allowed)
address(LICENSING_MODULE), // Target module
LICENSING_MODULE.attachLicenseTerms.selector, // Function selector
1 // ALLOW
);
// Allow 'operator' to call ANY function on LicensingModule for this IP
ACCESS_CONTROLLER.setPermission(
ipId, operator, address(LICENSING_MODULE), bytes4(0), 1
);
// Allow 'operator' to call ANY function on ANY module for this IP
ACCESS_CONTROLLER.setPermission(
ipId, operator, address(0), bytes4(0), 1
);Permission Resolution Order
- Specific permission:
(ipAccount, signer, module, function) - Module wildcard:
(ipAccount, signer, module, bytes4(0)) - Global wildcard:
(ipAccount, signer, address(0), bytes4(0)) - Default: DENY
Only the IP Account owner (NFT holder) can call setPermission.
SPG Multicall Pattern
SPG workflow contracts inherit OpenZeppelin's Multicall, allowing you to batch multiple workflow calls in a single transaction.
Built-in Multicall Usage
import { RegistrationWorkflows } from "@story-protocol/periphery/workflows/RegistrationWorkflows.sol";
import { WorkflowStructs } from "@story-protocol/periphery/lib/WorkflowStructs.sol";
// Encode multiple calls
bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeCall(
RegistrationWorkflows.mintAndRegisterIp,
(spgNftContract, recipient, ipMetadata1, true)
);
calls[1] = abi.encodeCall(
RegistrationWorkflows.mintAndRegisterIp,
(spgNftContract, recipient, ipMetadata2, true)
);
// Execute all in one tx via SPG's built-in multicall
bytes[] memory results = RegistrationWorkflows(
0xbe39E1C756e921BD25DF86e7AAa31106d1eb0424
).multicall(calls);
// Decode results
(address ipId1, uint256 tokenId1) = abi.decode(results[0], (address, uint256));
(address ipId2, uint256 tokenId2) = abi.decode(results[1], (address, uint256));Why Multicall3 Does NOT Work With SPGNFT Minting
The standard Multicall3 contract at 0xcA11bde05977b3631167028862bE2a173976CA11 uses delegatecall to execute batched calls. This changes msg.sender to the Multicall3 contract address instead of the original caller.
SPGNFT minting functions check msg.sender for minting rights. When called through Multicall3:
- User calls
Multicall3.aggregate3() - Multicall3 calls
RegistrationWorkflows.mintAndRegisterIp()via delegatecall - Inside RegistrationWorkflows,
msg.senderis nowMulticall3address - SPGNFT rejects the mint because Multicall3 has no minting rights
Solution: Always use the SPG contract's built-in multicall() function. It uses delegatecall internally but preserves the correct msg.sender context because the calls execute within the SPG contract itself.
Common Pitfalls
- Multicall3 with SPGNFT: Never use the standard Multicall3 for SPG operations that involve SPGNFT minting.
msg.senderchanges to the Multicall3 contract address, breaking minting permission checks. Use SPG's built-inmulticall()instead. - msg.sender in delegatecall: When using
delegatecall,msg.senderrefers to the contract executing the delegatecall, not the original EOA. This affects all permission checks in Story Protocol modules. - Gas estimation with fork tests: Aeneid fork tests may show different gas costs than mainnet. Always set appropriate gas limits and use
--gas-limitflag if tests run out of gas. - ipId vs tokenId: The
ipIdis the IP Account contract address (ERC-6551 TBA), NOT the ERC-721 token ID. UseIPAssetRegistry.ipId()to derive it. - License terms must exist before attaching: You must first register license terms via
PILicenseTemplate.registerLicenseTerms()before callingLicensingModule.attachLicenseTerms(). - ERC-20 approvals for royalties: Before calling
payRoyaltyOnBehalf, the caller must approve the RoyaltyModule to spend the payment token. - Permission before module calls: If a third-party contract calls a module on behalf of an IP, the IP owner must first grant permission via AccessController. Without this, the call reverts with
AccessController__PermissionDenied. - SignatureData for SPG with existing NFTs: When using SPG to register an existing NFT, you must provide EIP-712 signatures (SignatureData) to authorize the SPG contract to act on behalf of the NFT owner.
Network Configuration
| Network | Chain ID | RPC | Explorer |
| Aeneid (testnet) | 1315 | https://aeneid.storyrpc.io | https://aeneid.storyscan.io |
| Mainnet | 1514 | https://mainnet.storyrpc.io | https://mainnet.storyscan.xyz |
Import Paths
Core contracts use the @story-protocol/core package, periphery contracts use @story-protocol/periphery:
// Core
import { IPAssetRegistry } from "@story-protocol/core/registries/IPAssetRegistry.sol";
import { LicensingModule } from "@story-protocol/core/modules/licensing/LicensingModule.sol";
import { PILicenseTemplate } from "@story-protocol/core/modules/licensing/PILicenseTemplate.sol";
import { RoyaltyModule } from "@story-protocol/core/modules/royalty/RoyaltyModule.sol";
import { DisputeModule } from "@story-protocol/core/modules/dispute/DisputeModule.sol";
import { AccessController } from "@story-protocol/core/access/AccessController.sol";
import { CoreMetadataModule } from "@story-protocol/core/modules/metadata/CoreMetadataModule.sol";
import { GroupingModule } from "@story-protocol/core/modules/grouping/GroupingModule.sol";
// Periphery / SPG
import { RegistrationWorkflows } from "@story-protocol/periphery/workflows/RegistrationWorkflows.sol";
import { DerivativeWorkflows } from "@story-protocol/periphery/workflows/DerivativeWorkflows.sol";
import { LicenseAttachmentWorkflows } from "@story-protocol/periphery/workflows/LicenseAttachmentWorkflows.sol";
import { GroupingWorkflows } from "@story-protocol/periphery/workflows/GroupingWorkflows.sol";
import { RoyaltyWorkflows } from "@story-protocol/periphery/workflows/RoyaltyWorkflows.sol";
import { WorkflowStructs } from "@story-protocol/periphery/lib/WorkflowStructs.sol";Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

