Skip to main content
This guide details the complete process for licensing an existing, verified digital asset (Level 2: Verified Rights) and then minting a new Licensed Creative Asset based on that license. It primarily uses the Mock Licensing Protocol provided by the SDK for testing purposes on Cultura testnets, demonstrating the interactions involved.

Goal

To create a new digital asset (Licensed Creative Asset) that is verifiably derived from one or more parent assets (Licensable Digital Assets) according to specific license terms, embedding provenance and royalty information on-chain.

Prerequisites

  • A Licensable Digital Asset exists (Level 2 Verified Rights). Let’s call this the “Parent Asset”. Its collectionAddress and tokenId are known.
  • The user wanting to create the derivative work (the “Licensee”) has an account with funds (ETH for gas, Bond Tokens for attestation/verification).
  • The SDK is initialized for the Licensee (CulturaSDK.createWithWallet or CulturaSDK.createWithAccount).

Conceptual Steps

  1. Obtain License: Secure the rights to use the Parent Asset(s) via a Licensing Protocol.
  2. Mint Derivative: Create the new Licensed Creative Asset, linking it to the Parent Asset(s) and embedding royalty splits.
  3. Attest Derivative: Create an attestation for the new Licensed Creative Asset to establish its own rights record (Level 1).
  4. Verify Derivative (Optional): Verifiers can endorse the new asset’s attestation, potentially promoting it to Level 2.

Flow Using Mock Licensing Protocol (Testnets)

The SDK provides a MockLicensingProtocolClient to simulate Step 1 and streamline Steps 2 & 3 for testing.

Flow Diagram

Explanation

  1. Simulate License Acquisition (Mock Protocol):
    • The Licensee selects a Parent Asset (must be Level 2) via the dApp.
    • The dApp calls sdk.mockLicensingProtocol.licenseDigitalAsset().
    • The mock contract records that this parent asset is “licensed” for testing. (A real protocol would handle actual terms, payments, etc.).
  2. Prepare for Delegated Attestation:
    • The new Licensed Creative Asset needs attestation by its creator (Licensee). Since the Mock Protocol’s convenience function (createLicensedAsset) handles minting and attestation together, it requires the Licensee’s signature beforehand.
    • The dApp calls sdk.attestationService.signForDelegatedAttestation() to get the necessary EIP-712 typed data.
    • The Licensee signs this data via their wallet.
    • The dApp formats the signature using sdk.attestationService.generateSignatureParams().
  3. Create Licensed Asset (Mock Protocol Convenience Function):
    • The dApp calls sdk.mockLicensingProtocol.createLicensedAsset(). This function bundles several actions:
      • Input: Takes new asset details, the SignatureParams from Step 2, a schemaUID, bondAmount, grade for the new attestation, and parentRights info (parent collection/ID, owner, royaltySplit).
      • Metadata: Uploads metadata for the new asset to IPFS.
      • Minting: Calls sdk.culturaDigitalAsset.mintDigitalAsset() to create the new Licensed Creative Asset NFT, passing the parentRights array to link it to the parent(s) and embed royalty info.
      • Delegated Attestation: Calls sdk.attestationService.delegatedAttest() using the Licensee’s signature to create the attestation (Level 1) for the new asset and deploy its RBA.
    • Returns the licensedAssetId and attestationInfo (attestationUid, rightsBoundAccount).
  4. Optional Verification (Mock Protocol):
    • To simulate the licensing protocol verifying the new asset’s compliance, a Verifier calls sdk.mockLicensingProtocol.verifyLicensedAsset().
    • This takes the attestationUID of the newly created asset and a verificationBond.
    • Internally, the mock protocol calls sdk.attestationService.verifyAttestation() on the CAS contract, acting as a verifier for the new asset’s attestation. This could promote the new asset to Level 2.
  5. Result: The Licensed Creative Asset is minted and attested (Level 1), ready for royalty processing or further verification.

Direct Minting (Without Mock Protocol)

If using a real licensing protocol or handling licensing off-chain:
  1. Secure the license for the Level 2 Parent Asset(s).
  2. Gather the required parentAssets data (collection, ID, owner, royaltySplit) based on the license terms.
  3. Prepare metadata objects for the new derivative asset (termsObject, tokenUriObject).
  4. Call sdk.culturaDigitalAsset.mintDigitalAsset() directly, providing the recipient, new asset details, the populated parentAssets array, and the termsObject and tokenUriObject (or pre-uploaded termsURI and tokenURI strings). The SDK handles IPFS uploads if objects are provided.
  5. Optionally, call sdk.attestationService.attest() to attest the newly minted asset (Level 0 -> Level 1).
  6. Optionally, have verifiers call sdk.attestationService.verifyAttestation() to promote the new asset (Level 1 -> Level 2).

Registration Patterns

How the metadata (termsURI) for the Licensed Creative Asset is structured depends on the use case:
  1. Direct Representation: The Licensed Creative Asset NFT is the product (e.g., a derivative artwork). Its metadata points directly to the artwork file and describes the artwork itself. The link to the parent(s) is embedded via the parentDigitalAssets array stored by the CulturaDigitalAsset contract during minting.
  2. Reference Representation: The Licensed Creative Asset NFT acts as a license token or voucher. Its metadata describes the license terms and might link to an external product (which could be off-chain, a different NFT collection, or require further action). The parentDigitalAssets array still links to the original Licensable Digital Asset.
The choice depends on whether the on-chain token directly embodies the licensed output or merely represents the right to access/use it elsewhere.

SDK Snippets

import { CulturaSDK, ParentDigitalAsset } from "@cultura/sdk";
import { parseEther, getAddress } from "viem"; // Example utility

/**
 * Simulates licensing a parent asset using the Mock Protocol.
 * Assumes sdk is initialized for the LICENSEE.
 */
async function simulateLicenseAcquisition(
  sdk: CulturaSDK,
  parentCollection: `0x${string}`,
  parentTokenId: bigint
) {
  try {
    console.log(
      `Simulating license acquisition for ${parentCollection}#${parentTokenId}...`
    );
    // Ensure the mock protocol client is available
    if (!sdk.mockLicensingProtocol) {
      throw new Error(
        "Mock Licensing Protocol client not available in SDK config."
      );
    }
    const txHash = await sdk.mockLicensingProtocol.licenseDigitalAsset(
      parentCollection,
      parentTokenId
    );
    await sdk.publicClient.waitForTransactionReceipt({ hash: txHash });
    console.log("Mock license acquired, Tx:", txHash);
  } catch (error) {
    console.error("Error simulating license acquisition:", error);
    throw error;
  }
}

/**
 * Creates and attests a Licensed Creative Asset using the Mock Protocol's
 * convenience function.
 * Assumes sdk is initialized for the LICENSEE.
 */
async function createAndAttestLicensedAsset(
  sdk: CulturaSDK,
  licenseeAddress: `0x${string}`,
  parentRights: readonly ParentDigitalAsset[], // Info about the licensed parent(s) including royaltySplit
  newAssetName: string,
  newAssetDescription: string,
  newAssetImageUrl?: string
) {
  try {
    // Ensure necessary SDK clients are available
    if (
      !sdk.attestationService ||
      !sdk.schemaRegistry ||
      !sdk.bondToken ||
      !sdk.mockLicensingProtocol ||
      !sdk.culturaDigitalAsset
    ) {
      throw new Error(
        "Required SDK clients (Attestation, Schema, BondToken, MockLicensing, DigitalAsset) not configured."
      );
    }

    // --- Prepare for Delegated Attestation ---
    console.log("Preparing signature for delegated attestation...");
    const signatureParams =
      await sdk.attestationService.signForDelegatedAttestation(licenseeAddress);
    console.log("Signature obtained.");

    // --- Define Attestation Parameters ---
    const schemaStr =
      "string digitalAssetName, string digitalAssetDescription, uint256 grade"; // Example schema
    const schemaUID = await sdk.schemaRegistry.getOrRegisterSchema(
      schemaStr,
      true // Assuming revocable
    );
    const grade = 3n; // Example: Grade C for the new attestation
    const bondAmount = parseEther("0.0001"); // Example bond for Grade C attestation fee

    // --- Approve Bond Tokens (Licensee needs to approve CAS for the attestation bond) ---
    const casAddress = sdk.config.contracts?.attestationService;
    if (!casAddress) throw new Error("CAS address not configured");
    console.log(`Approving ${bondAmount} tokens for CAS...`);
    const approveTx = await sdk.bondToken.approve(casAddress, bondAmount);
    await sdk.publicClient.waitForTransactionReceipt({ hash: approveTx });
    console.log("Bond token approved for attestation.");

    // --- Call Mock Protocol's createLicensedAsset ---
    console.log("Calling createLicensedAsset on Mock Protocol...");
    // This function bundles minting and attestation
    const result = await sdk.mockLicensingProtocol.createLicensedAsset(
      {
        name: newAssetName,
        description: newAssetDescription,
        imageUrl: newAssetImageUrl,
      },
      signatureParams,
      schemaUID,
      bondAmount, // Pass the bond amount required for the attestation
      grade,
      parentRights
      // externalNFT parameter can be added here if licensing an external asset
    );

    console.log("createLicensedAsset call successful.");
    console.log(`  New Licensed Asset ID: ${result.licensedAssetId}`);
    console.log(`  Attestation UID: ${result.attestationInfo.attestationUid}`);
    console.log(
      `  Rights Bound Account: ${result.attestationInfo.rightsBoundAccount}`
    );
    console.log(`  Transaction State: ${result.transactionState}`); // Should be 'complete'

    if (result.transactionState !== "complete") {
      throw new Error("createLicensedAsset did not complete successfully.");
    }

    return result;
  } catch (error) {
    console.error("Error creating licensed asset:", error);
    throw error;
  }
}

/**
 * Optional: Simulate verification of the newly created Licensed Creative Asset's attestation.
 * Assumes sdk is initialized for a VERIFIER.
 */
async function simulateVerificationOfLicensedAsset(
  sdk: CulturaSDK, // SDK instance for the VERIFIER
  attestationUID: `0x${string}`,
  verificationBond: bigint // e.g., parseEther("0.01")
) {
  try {
    // Ensure necessary SDK clients are available
    if (
      !sdk.bondToken ||
      !sdk.mockLicensingProtocol ||
      !sdk.attestationService
    ) {
      throw new Error(
        "Required SDK clients (BondToken, MockLicensing, Attestation) not configured."
      );
    }
    // --- Approve Bond Tokens (Verifier needs to approve CAS) ---
    // The mock protocol's verifyLicensedAsset calls CAS.verifyAttestation internally.
    const casAddress = sdk.config.contracts?.attestationService;
    if (!casAddress) throw new Error("CAS address not configured");

    console.log(
      `Approving ${verificationBond} tokens for verification via CAS...`
    );
    const approveTx = await sdk.bondToken.approve(casAddress, verificationBond);
    await sdk.publicClient.waitForTransactionReceipt({ hash: approveTx });
    console.log("Bond token approved for verification.");

    // --- Call Mock Protocol's verifyLicensedAsset ---
    console.log(
      `Calling verifyLicensedAsset for attestation ${attestationUID}...`
    );
    // This simulates the licensing protocol verifying the attestation
    const verifyTxHash = await sdk.mockLicensingProtocol.verifyLicensedAsset(
      attestationUID,
      verificationBond
    );
    await sdk.publicClient.waitForTransactionReceipt({ hash: verifyTxHash });
    console.log("Verification simulation complete, Tx:", verifyTxHash);

    // You could potentially query the asset's level again here to see if it reached Level 2
    // const attestationData = await sdk.attestationService.getAttestation(attestationUID);
    // const level = await sdk.attestationService.getDigitalAssetLevel(attestationData.digitalAssetAddress, attestationData.digitalAssetId);
    // console.log("Asset level after mock verification:", level);
  } catch (error) {
    console.error("Error verifying licensed asset:", error);
    throw error;
  }
}

// --- Example Usage Flow ---
// async function runFullLicensingFlow() {
//   // Assume licenseeSdk is initialized and connected
//   const licenseeSdk = CulturaSDK.createWithWallet(window.ethereum, { chain: 'testnet' });
//   if (!licenseeSdk.walletClient?.account) {
//     console.error("Licensee wallet not connected");
//     return;
//   }
//   const licenseeAddress = licenseeSdk.walletClient.account.address;

//   // Define Parent Asset Info
//   const parentCollection = getAddress('0xParentContractAddress...'); // Use getAddress for checksum
//   const parentTokenId = 1n;
//   const parentOwner = getAddress('0xParentOwnerAddress...'); // Use getAddress

//   try {
//     // 1. Verify Parent Asset is Level 2 (Example Check)
//     const parentLevel = await licenseeSdk.attestationService.getDigitalAssetLevel(parentCollection, parentTokenId);
//     if (parentLevel !== 2) {
//       console.error(`Parent asset ${parentTokenId} is not Level 2.`);
//       return;
//     }
//     console.log("Parent asset confirmed Level 2.");

//     // 2. Simulate getting the license via Mock Protocol
//     await simulateLicenseAcquisition(licenseeSdk, parentCollection, parentTokenId);

//     // 3. Define parent info for the new asset, including royalty split
//     const parentRightsInfo: readonly ParentDigitalAsset[] = [{
//       parentCollection: parentCollection,
//       parentDigitalAssetId: parentTokenId,
//       owner: parentOwner,
//       royaltySplit: 5000n // 50% royalty split example (5000 basis points)
//     }];

//     // 4. Create and attest the new licensed asset using the mock protocol's convenience function
//     const { licensedAssetId, attestationInfo } = await createAndAttestLicensedAsset(
//       licenseeSdk,
//       licenseeAddress,
//       parentRightsInfo,
//       "My Licensed Derivative",
//       "Based on Parent Asset #1",
//       "ipfs://..." // Optional image URL for the new asset
//     );
//     console.log(`New asset ${licensedAssetId} created and attested (Level 1). Attestation: ${attestationInfo.attestationUid}`);

//     // 5. Optional: Have a verifier verify the new asset's attestation using the mock protocol
//     // Assume verifierSdk is initialized for a different account
//     // const verifierSdk = CulturaSDK.createWithWallet(...);
//     // await simulateVerificationOfLicensedAsset(verifierSdk, attestationInfo.attestationUid, parseEther("0.02"));
//     // console.log(`Attestation ${attestationInfo.attestationUid} submitted for verification.`);

//   } catch (e) {
//     console.error("Full licensing flow failed:", e);
//   }
// }

// runFullLicensingFlow();