Skip to content

Instantly share code, notes, and snippets.

@hydrogenbond007
Created March 2, 2026 17:31
Show Gist options
  • Select an option

  • Save hydrogenbond007/01636d4b2ce7cc07dd2ab0ddf4297dba to your computer and use it in GitHub Desktop.

Select an option

Save hydrogenbond007/01636d4b2ce7cc07dd2ab0ddf4297dba to your computer and use it in GitHub Desktop.
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.

Origin Protocol IP-NFT Integration Guide

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

Core Concepts

What Origin Provides

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() or buyAccessSmart()
  • Access Validation: Check if wallet has valid license
  • Royalty Distribution: Automatic payment routing to creators + parent IPs
  • Attribution Chains: Track derivative relationships permanently

License Types

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.

Integration Architecture

Typical Flow

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

Key SDK Methods

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
);

Purchase Flow Patterns

Pattern 1: Gated Content Access

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
    }
  };
}

Pattern 2: Two-Step Purchase + Delivery

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)
  };
}

Pattern 3: Subscription Renewal

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 };
}

Common Pitfalls

1. Purchase Loops

Problem: User buys access but app keeps prompting purchase

Root causes:

  • Not updating local state after purchase
  • Checking stale access status
  • Sending isOwned: false after 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 });

2. Terms Mismatch Reverts

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
);

3. Access Check Before Content Delivery

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);
}

4. Ignoring Parent IP Attribution

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)`);
  }
}

Testing Strategies

1. Ownership Verification

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);

2. Expiry Testing (DURATION_BASED)

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);

3. Purchase Flow End-to-End

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 phrase

Content Delivery Patterns

Pattern 1: Direct Content Injection

Use 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;
}

Pattern 2: Streaming Access

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);
}

Pattern 3: API Gateway

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 });
}

Environment Setup

Required Environment Variables

# 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_key

SDK Configuration

import { CampProvider } from '@campnetwork/origin/react';

<CampProvider
  environment="PRODUCTION" // or "DEVELOPMENT"
  clientId={process.env.NEXT_PUBLIC_AUTH_HUB_CLIENT_ID}
>
  {children}
</CampProvider>

Debugging Checklist

Purchase Not Working

  • 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?

Access Check Failing

  • Is tokenId correct?
  • Is walletAddress correct (not contract address)?
  • Has transaction actually confirmed?
  • Is this a DURATION_BASED license that expired?
  • Are you checking on correct network (testnet vs mainnet)?

Content Not Delivering

  • Did you verify access before fetching content?
  • Is metadataURI resolvable (IPFS gateway working)?
  • Are you handling purchase approval state correctly?
  • Is local state updated after purchase?

Advanced: Royalty Distribution

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 }

Resources

Expected Outcome

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment