| name | sdk-integration |
| description | Story Protocol TypeScript SDK setup and usage. Use when user mentions "Story SDK", "@story-protocol/core-sdk", "StoryClient", "SDK setup", "story-protocol npm", or wants to initialize and use the Story Protocol SDK. |
| allowed-tools | Read, Glob, Grep, WebFetch, Task(subagent_type:Explore) |
| model | opus |
| license | MIT |
| metadata | {"author":"story-protocol","version":"0.1.0"} |
Story Protocol TypeScript SDK
Complete guide for installing, configuring, and using the @story-protocol/core-sdk package.
Installation
npm install @story-protocol/core-sdk viem
Requirements:
- Node.js 18+
- npm 8+
- An EVM wallet private key (for backend) or wallet connector (for frontend)
- RPC endpoint for Story Protocol network
Client Initialization
StoryConfig Type
import { StoryConfig } from '@story-protocol/core-sdk';
Backend Setup (Node.js)
import { http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { StoryClient, StoryConfig } from '@story-protocol/core-sdk';
const account = privateKeyToAccount(`0x${process.env.WALLET_PRIVATE_KEY}`);
const config: StoryConfig = {
account,
transport: http('https://aeneid.storyrpc.io'),
chainId: 'aeneid',
};
const client = StoryClient.newClient(config);
Frontend Setup (Browser Wallet)
import { custom } from 'viem';
import { StoryClient, StoryConfig } from '@story-protocol/core-sdk';
const config: StoryConfig = {
account: walletClient.account,
transport: custom(window.ethereum),
chainId: 'aeneid',
};
const client = StoryClient.newClient(config);
Chain Options
| Chain | chainId Value | Numeric Chain ID | RPC URL | Explorer |
|---|
| Aeneid (testnet) | 'aeneid' | 1315 | https://aeneid.storyrpc.io | https://aeneid.storyscan.io |
| Mainnet | 'mainnet' | 1514 | https://mainnet.storyrpc.io | https://mainnet.storyscan.xyz |
Client Modules
The StoryClient instance exposes specialized sub-clients for each protocol domain:
| Module | Accessor | Class | Primary Methods |
|---|
| IP Assets | client.ipAsset | IPAssetClient | registerIpAsset, registerDerivativeIpAsset, linkDerivative |
| Licensing | client.license | LicenseClient | registerPILTerms, attachLicenseTerms, mintLicenseTokens, getLicenseTerms |
| Royalties | client.royalty | RoyaltyClient | payRoyaltyOnBehalf, claimAllRevenue, batchClaimAllRevenue |
| Disputes | client.dispute | DisputeClient | raiseDispute, resolveDispute |
| NFT Collections | client.nftClient | NftClient | createNFTCollection |
| Groups | client.groupClient | GroupClient | registerGroup, addIpToGroup, removeIpFromGroup, claimReward |
| WIP Token | client.wipClient | WipClient | deposit, withdraw, approve, transfer |
| IP Accounts | client.ipAccountClient | IPAccountClient | execute, executeWithSig, getIpAccountNonce |
| Permissions | client.permissionClient | PermissionClient | setPermission, setBatchPermissions, createSetPermissionSignature |
IPAssetClient
const result = await client.ipAsset.registerIpAsset({
nft: {
type: 'minted',
nftContract: '0xYourERC721',
tokenId: '1',
},
ipMetadata: {
ipMetadataURI: 'https://ipfs.io/ipfs/...',
ipMetadataHash: '0x...',
nftMetadataURI: 'https://ipfs.io/ipfs/...',
nftMetadataHash: '0x...',
},
});
console.log(`ipId: ${result.ipId}, tx: ${result.txHash}`);
const result2 = await client.ipAsset.registerIpAsset({
nft: {
type: 'mint',
spgNftContract: '0xYourSPGCollection',
},
ipMetadata: { },
});
const result3 = await client.ipAsset.registerDerivativeIpAsset({
nft: {
type: 'mint',
spgNftContract: '0xYourSPGCollection',
},
derivData: {
parentIpIds: ['0xParentIpId'],
licenseTermsIds: ['1'],
},
ipMetadata: { },
});
LicenseClient
import { PILFlavor, WIP_TOKEN_ADDRESS } from '@story-protocol/core-sdk';
import { parseEther } from 'viem';
const result = await client.license.registerPILTerms({
terms: PILFlavor.commercialRemix({
commercialRevShare: 10,
defaultMintingFee: parseEther('0.1'),
currency: WIP_TOKEN_ADDRESS,
}),
});
console.log(`License Terms ID: ${result.licenseTermsId}`);
await client.license.attachLicenseTerms({
ipId: '0xYourIpId',
licenseTermsId: result.licenseTermsId,
});
const mintResult = await client.license.mintLicenseTokens({
licensorIpId: '0xParentIpId',
licenseTermsId: '1',
amount: 1,
receiver: '0xRecipient',
});
RoyaltyClient
import { WIP_TOKEN_ADDRESS } from '@story-protocol/core-sdk';
import { parseEther } from 'viem';
await client.royalty.payRoyaltyOnBehalf({
receiverIpId: '0xReceiverIpId',
payerIpId: '0xPayerIpId',
token: WIP_TOKEN_ADDRESS,
amount: parseEther('1'),
});
await client.royalty.claimAllRevenue({
ancestorIpId: '0xAncestorIpId',
claimer: '0xClaimerAddress',
childIpIds: ['0xChildIp1', '0xChildIp2'],
royaltyPolicies: ['0xPolicyAddress1', '0xPolicyAddress2'],
currencyTokens: [WIP_TOKEN_ADDRESS],
});
await client.royalty.batchClaimAllRevenue({
ancestorIpId: '0xAncestorIpId',
claimer: '0xClaimerAddress',
childIpIds: ['0xChildIp1'],
royaltyPolicies: ['0xPolicyAddress1'],
currencyTokens: [WIP_TOKEN_ADDRESS],
});
DisputeClient
const dispute = await client.dispute.raiseDispute({
targetIpId: '0xTargetIpId',
disputeEvidenceHash: '0xEvidenceHash',
targetTag: 'PLAGIARISM',
bond: parseEther('0.1'),
});
console.log(`Dispute ID: ${dispute.disputeId}`);
await client.dispute.resolveDispute({
disputeId: dispute.disputeId,
data: '0x',
});
NftClient
import { zeroAddress } from 'viem';
const collection = await client.nftClient.createNFTCollection({
name: 'My IP Collection',
symbol: 'MIP',
isPublicMinting: false,
mintOpen: true,
mintFeeRecipient: zeroAddress,
contractURI: '',
});
console.log(`Collection: ${collection.spgNftContract}`);
GroupClient
const group = await client.groupClient.registerGroup({
groupPool: '0xf96f2c30b41Cb6e0290de43C8528ae83d4f33F89',
});
console.log(`Group IP ID: ${group.groupIpId}`);
await client.groupClient.addIpToGroup({
groupIpId: group.groupIpId,
ipIds: ['0xIpId1', '0xIpId2'],
});
WipClient
await client.wipClient.deposit({
amount: parseEther('1'),
});
await client.wipClient.withdraw({
amount: parseEther('0.5'),
});
await client.wipClient.approve({
spender: '0xSpenderAddress',
amount: parseEther('10'),
});
Key Exports
The @story-protocol/core-sdk package exports the following:
Classes and Constructors
| Export | Description |
|---|
StoryClient | Main client class; use StoryClient.newClient(config) |
StoryConfig | Configuration type for client initialization |
Constants
| Export | Description |
|---|
WIP_TOKEN_ADDRESS | Wrapped IP token address (0x1514000000000000000000000000000000000000) |
royaltySharesTotalSupply | Total supply of royalty shares (100,000,000) |
MAX_ROYALTY_TOKEN | Maximum royalty token value |
defaultFunctionSelector | Default function selector for permissions |
License Utilities
| Export | Description |
|---|
PILFlavor | Helper for creating PIL license terms: .nonCommercialSocialRemixing(), .commercialUse(), .commercialRemix() |
LicenseTerms | TypeScript type for license terms configuration |
Chain Objects
| Export | Description |
|---|
aeneid | Aeneid testnet chain configuration object (chain ID 1315) |
mainnet | Mainnet chain configuration object (chain ID 1514) |
Utility Functions
| Export | Description |
|---|
convertCIDtoHashIPFS | Convert an IPFS CID to its hash representation |
convertHashIPFStoCID | Convert an IPFS hash back to a CID |
getPermissionSignature | Generate a permission signature for IP Account operations |
getSignature | Generate a generic signature for SDK operations |
Common Patterns
Error Handling
try {
const result = await client.ipAsset.registerIpAsset({ });
console.log(`Success: ${result.ipId}`);
} catch (error) {
if (error.message.includes('execution reverted')) {
console.error('Transaction reverted:', error.message);
} else if (error.message.includes('insufficient funds')) {
console.error('Not enough IP tokens for gas');
} else {
console.error('Unexpected error:', error);
}
}
Transaction Confirmation
All write methods return a transaction hash. The SDK waits for confirmation by default:
const result = await client.ipAsset.registerIpAsset({ });
Deadline Parameter
For time-sensitive operations, use the deadline parameter to set a transaction expiration:
const result = await client.ipAsset.registerIpAsset({
nft: { type: 'mint', spgNftContract: '0x...' },
ipMetadata: { },
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
});
Batch Operations
Multiple operations can be combined using the SDK's built-in multicall support where available, or by sequencing individual calls:
const ip1 = await client.ipAsset.registerIpAsset({ });
const ip2 = await client.ipAsset.registerIpAsset({ });
await client.license.attachLicenseTerms({ ipId: ip1.ipId, licenseTermsId: '1' });
await client.license.attachLicenseTerms({ ipId: ip2.ipId, licenseTermsId: '1' });
Viem Integration
The Story Protocol SDK is built on top of viem and uses viem types throughout:
Transport Options
import { http, webSocket, custom } from 'viem';
const config: StoryConfig = {
account,
transport: http('https://aeneid.storyrpc.io'),
chainId: 'aeneid',
};
const config: StoryConfig = {
account,
transport: webSocket('wss://aeneid.storyrpc.io'),
chainId: 'aeneid',
};
const config: StoryConfig = {
account,
transport: custom(window.ethereum),
chainId: 'aeneid',
};
Viem Utility Functions
The SDK works seamlessly with viem utilities:
import { parseEther, formatEther, zeroAddress, isAddress } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
const mintingFee = parseEther('0.1');
const formatted = formatEther(balance);
const isValid = isAddress('0x...');
const account = privateKeyToAccount('0x...');
Environment Variable Pattern
import { http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { StoryClient, StoryConfig } from '@story-protocol/core-sdk';
const account = privateKeyToAccount(`0x${process.env.WALLET_PRIVATE_KEY}`);
const config: StoryConfig = {
account,
transport: http(process.env.STORY_RPC_URL || 'https://aeneid.storyrpc.io'),
chainId: (process.env.STORY_CHAIN as 'aeneid' | 'mainnet') || 'aeneid',
};
const client = StoryClient.newClient(config);
Network Configuration
| Network | Chain ID | RPC | Explorer | Faucet |
|---|
| Aeneid (testnet) | 1315 | https://aeneid.storyrpc.io | https://aeneid.storyscan.io | https://aeneid.faucet.story.foundation/ |
| Mainnet | 1514 | https://mainnet.storyrpc.io | https://mainnet.storyscan.xyz | N/A |
Common Pitfalls
- Missing viem dependency: Always install both
@story-protocol/core-sdk and viem. The SDK will not work without viem.
- Private key format:
privateKeyToAccount expects a 0x-prefixed hex string. If your env var does not include the prefix, add it: `0x${process.env.WALLET_PRIVATE_KEY}`
- Wrong chainId type: The
chainId field in StoryConfig expects a string ('aeneid' or 'mainnet'), not a number.
- Insufficient gas: Write operations require native IP tokens for gas. Get testnet tokens from the faucet.
- Transaction deadline: If a transaction is submitted after the deadline, it will revert. Omit the deadline for non-time-sensitive operations.
- BigInt values: Monetary values (minting fees, royalty amounts) must be
BigInt. Use parseEther() from viem to convert.