| name | description |
|---|---|
origin-ipnft-integration |
Use this skill when building applications that integrate with Camp Origin Protocol for IP-NFT minting, discovery, licensing, and content delivery. Covers SDK usage, purchase flows, access validation, and common integration patterns. |
Use this skill when building:
- Applications that mint IP-NFTs on Origin
- Marketplaces for discovering and licensing IP
- Tools that need to validate IP-NFT ownership/access
- Agents or services that consume licensed content
- Integrations with Origin's licensing infrastructure
Origin Protocol handles:
- IP Registration: Mint content as IP-NFTs with embedded license terms
- License Terms: Price, duration, royalty splits, payment tokens (onchain)
- Purchase Flow: Atomic licensing via
buyAccess()orbuyAccessSmart() - Access Validation: Check if wallet has valid license
- Royalty Distribution: Automatic payment routing to creators + parent IPs
- Attribution Chains: Track derivative relationships permanently
Three license models exist:
SINGLE_PAYMENT: Pay once, perpetual access (type 0)DURATION_BASED: Time-limited subscription (type 1)X402: Dynamic AI agent negotiation (type 2)
Check license type before building purchase UX.
1. Discovery
- User searches/browses IP-NFTs
- App fetches metadata from Origin
2. Preview
- Show license terms (price, duration, royalty)
- Display creator info, parent IPs, derivatives
3. Purchase Check
- Validate: Does user already own license?
- If yes: Grant access immediately
- If no: Prompt purchase
4. Purchase Execution
- User approves transaction
- Call origin.buyAccessSmart() or buyAccess()
- Wait for confirmation
5. Access Grant
- Verify ownership onchain or via SDK
- Deliver licensed content/access
- Track usage for analytics
Origin SDK (React/TypeScript):
import { useOrigin } from '@campnetwork/origin/react';
// Search IP-NFTs
const { data } = await origin.searchIPNFTs(query, filters);
// Get IP-NFT details
const ipnft = await origin.getIPNFT(tokenId);
// Check access (does wallet have license?)
const hasAccess = await origin.hasAccess(tokenId, walletAddress);
// Purchase license
const tx = await origin.buyAccessSmart(
tokenId,
expectedPrice,
expectedDuration,
expectedPaymentToken
);
// Mint new IP-NFT
const newTokenId = await origin.mint(
metadataURI,
licenseTerms,
parentIPs,
royaltyShares
);Use case: User wants to access content (skill, dataset, media)
async function handleContentRequest(tokenId, walletAddress) {
// 1. Check if user has access
const hasAccess = await origin.hasAccess(tokenId, walletAddress);
if (hasAccess) {
// Grant access immediately
return { status: 'GRANTED', content: fetchContent(tokenId) };
}
// 2. Return purchase prompt
const ipnft = await origin.getIPNFT(tokenId);
return {
status: 'PURCHASE_REQUIRED',
terms: {
price: ipnft.price,
duration: ipnft.duration,
royaltyBps: ipnft.royaltyBps
}
};
}Use case: Purchase requires user approval, then content delivery
// Step 1: User initiates purchase
async function initiatePurchase(tokenId) {
const ipnft = await origin.getIPNFT(tokenId);
// Validate expected terms (frontrun protection)
return {
status: 'AWAITING_APPROVAL',
tokenId,
price: ipnft.price,
duration: ipnft.duration
};
}
// Step 2: After wallet approval
async function completePurchase(tokenId, txHash) {
// Wait for transaction confirmation
await waitForTransaction(txHash);
// Verify access onchain
const hasAccess = await origin.hasAccess(tokenId, walletAddress);
if (!hasAccess) {
throw new Error('Purchase failed or not yet confirmed');
}
// Deliver content
return {
status: 'PURCHASED',
content: await fetchContent(tokenId)
};
}Use case: Check expiry and prompt renewal
async function checkSubscription(tokenId, walletAddress) {
const hasAccess = await origin.hasAccess(tokenId, walletAddress);
if (!hasAccess) {
return { status: 'EXPIRED', action: 'RENEW' };
}
// Check expiry date (for DURATION_BASED)
const expiryTimestamp = await origin.getExpiryTimestamp(tokenId, walletAddress);
const daysUntilExpiry = (expiryTimestamp - Date.now()) / (1000 * 60 * 60 * 24);
if (daysUntilExpiry < 7) {
return { status: 'EXPIRING_SOON', daysLeft: Math.floor(daysUntilExpiry) };
}
return { status: 'ACTIVE', expiryTimestamp };
}Problem: User buys access but app keeps prompting purchase
Root causes:
- Not updating local state after purchase
- Checking stale access status
- Sending
isOwned: falseafter successful purchase
Fix:
// After purchase transaction confirms
const purchaseApproved = true;
const isOwned = true; // CRITICAL: must be true now
// Update local state
setSelectedIPNFT({
...ipnft,
isOwned: true,
purchaseApproved: true
});
// Retry original request with updated ownership
handleRequest({ ...originalPayload, isOwned: true });Problem: Transaction reverts with "TermsMismatch" error
Cause: License terms changed between preview and purchase
Fix:
// Fetch fresh terms right before purchase
const ipnft = await origin.getIPNFT(tokenId);
// Pass expected values for frontrun protection
await origin.buyAccessSmart(
tokenId,
ipnft.price, // Must match current price
ipnft.duration, // Must match current duration
ipnft.paymentToken // Must match current token
);Problem: Delivering content before validating access
Risk: Users access paid content without paying
Fix:
async function deliverContent(tokenId, walletAddress) {
// ALWAYS verify access before delivery
const hasAccess = await origin.hasAccess(tokenId, walletAddress);
if (!hasAccess) {
throw new Error('Unauthorized: No valid license');
}
// Safe to deliver
return await fetchContent(tokenId);
}Problem: Not showing/respecting parent IPs in derivatives
Why it matters: Parent creators earn royalties from child IPs
Fix:
// When displaying IP-NFT
const ipnft = await origin.getIPNFT(tokenId);
// Show attribution chain
if (ipnft.parentIPs && ipnft.parentIPs.length > 0) {
console.log('This work derives from:');
for (const parentId of ipnft.parentIPs) {
const parent = await origin.getIPNFT(parentId);
console.log(`- ${parent.name} (${parent.royaltyShare}% royalty)`);
}
}Test that access checks work correctly:
// Test 1: User without license
const hasAccess1 = await origin.hasAccess(tokenId, unpaidWallet);
assert(hasAccess1 === false);
// Test 2: User purchases license
await origin.buyAccessSmart(tokenId, price, duration, token);
// Test 3: User now has access
const hasAccess2 = await origin.hasAccess(tokenId, paidWallet);
assert(hasAccess2 === true);Test subscription expiry:
// Purchase 1-day license
await origin.buyAccessSmart(tokenId, price, 86400, token);
// Immediately after purchase
const hasAccess1 = await origin.hasAccess(tokenId, wallet);
assert(hasAccess1 === true);
// Fast-forward time (testnet only)
await network.provider.send('evm_increaseTime', [86401]);
await network.provider.send('evm_mine');
// After expiry
const hasAccess2 = await origin.hasAccess(tokenId, wallet);
assert(hasAccess2 === false);Test complete flow with unique content:
// 1. Mint test IP-NFT with unique content
const uniquePhrase = "INTEGRATION_TEST_PHRASE_12345";
const metadataURI = uploadToIPFS({ content: uniquePhrase });
const tokenId = await origin.mint(metadataURI, terms);
// 2. Attempt access without license
let content = await tryFetchContent(tokenId, wallet);
assert(content === null); // Should be blocked
// 3. Purchase license
await origin.buyAccessSmart(tokenId, price, duration, token);
// 4. Fetch content with license
content = await fetchContent(tokenId, wallet);
assert(content.includes(uniquePhrase)); // Should contain unique phraseUse case: Agent/tool needs licensed content in context
async function injectLicensedContent(tokenId, walletAddress, contextBuilder) {
const hasAccess = await origin.hasAccess(tokenId, walletAddress);
if (!hasAccess) {
return contextBuilder; // No injection
}
const ipnft = await origin.getIPNFT(tokenId);
const content = await fetchFromIPFS(ipnft.metadataURI);
// Inject into context
contextBuilder.addSection('LICENSED_CONTENT', {
source: ipnft.name,
tokenId: tokenId,
content: content.text,
attribution: ipnft.creator
});
return contextBuilder;
}Use case: Media streaming with license validation
async function streamMedia(tokenId, walletAddress, res) {
// Validate access
const hasAccess = await origin.hasAccess(tokenId, walletAddress);
if (!hasAccess) {
return res.status(403).json({ error: 'License required' });
}
// Stream content
const ipnft = await origin.getIPNFT(tokenId);
const mediaStream = await fetchMediaStream(ipnft.metadataURI);
res.setHeader('Content-Type', ipnft.mediaType);
mediaStream.pipe(res);
}Use case: Proxy API calls through license check
async function proxyAPICall(tokenId, walletAddress, endpoint, params) {
// Check license
const hasAccess = await origin.hasAccess(tokenId, walletAddress);
if (!hasAccess) {
throw new Error('API access requires license');
}
// Track usage (optional)
await logUsage(tokenId, walletAddress, endpoint);
// Forward request
return await fetch(endpoint, { params });
}# Origin Protocol
NEXT_PUBLIC_ORIGIN_API_URL=https://api.origin.camp.network
NEXT_PUBLIC_CAMP_NETWORK_RPC=https://rpc.camp.network
# Authentication (if building backend)
JWT_SECRET=your_jwt_secret
# Optional: Analytics
ORIGIN_ANALYTICS_KEY=your_analytics_keyimport { CampProvider } from '@campnetwork/origin/react';
<CampProvider
environment="PRODUCTION" // or "DEVELOPMENT"
clientId={process.env.NEXT_PUBLIC_AUTH_HUB_CLIENT_ID}
>
{children}
</CampProvider>- Is wallet connected?
- Does wallet have enough CAMP tokens?
- Are expected terms matching current terms?
- Is transaction confirming onchain?
- Is
hasAccess()checking correct wallet address?
- Is
tokenIdcorrect? - Is
walletAddresscorrect (not contract address)? - Has transaction actually confirmed?
- Is this a DURATION_BASED license that expired?
- Are you checking on correct network (testnet vs mainnet)?
- Did you verify access before fetching content?
- Is
metadataURIresolvable (IPFS gateway working)? - Are you handling purchase approval state correctly?
- Is local state updated after purchase?
When users purchase licenses, Origin automatically distributes payments:
// Example: User pays 100 CAMP for license
// Origin distributes:
// - Protocol fee: 2.5 CAMP (2.5%)
// - App fee (if configured): 5 CAMP (5%)
// - Creator: 80 CAMP (80% after fees)
// - If derivative:
// - Child creator: 48 CAMP (60% of 80)
// - Parent IP: 24 CAMP (30% of 80)
// - Grandparent IP: 8 CAMP (10% of 80)To track earnings:
// Get total revenue for an IP-NFT
const revenue = await origin.getTotalRevenue(tokenId);
// Get revenue breakdown
const breakdown = await origin.getRevenueBreakdown(tokenId);
// Returns: { protocol, app, creator, parents }- Origin SDK Docs: https://docs.camp.network/origin
- IP-NFT Contract: https://github.com/campnetwork/origin-protocol
- Example Integration: https://github.com/campnetwork/origin-examples
When correctly integrated:
- Users can discover IP-NFTs via search/browse
- Purchase flow completes without errors
- Access validation prevents unauthorized use
- Licensed content delivers after purchase
- Attribution chains are visible and respected
- Creators receive automatic royalty payments