Skip to main content
This flow details how a digital asset progresses from its initial minted state (Level 0) to becoming attested (Level 1) and finally achieving Verified Rights (Level 2), making it licensable within the Cultura Proof of Rights Protocol.

Flow Diagram

Explanation

Part 1: Attestation (Level 0 -> Level 1)

  1. Owner Initiates: The owner of a minted digital asset (Level 0) uses a dApp to start the attestation process.
  2. Schema Handling (SDK): The SDK ensures the correct schema exists by calling sdk.schemaRegistry.getOrRegisterSchema(). This returns a schemaUID. This step is crucial for defining the structure of the attestation data.
  3. Bond Token Approval (SDK): The SDK prompts the owner’s wallet to approve the CASContract to spend the required bondAmount (determined by the chosen Grade) of the designated bond token. This calls sdk.bondToken.approve(). Approval must be granted before the attest call.
  4. Attestation Call (SDK): The SDK calls sdk.attestationService.attest(), providing:
    • attestationRequest: Contains the schemaUID, recipient (usually the owner), expiration time (0 for non-expiring), revocability flag, and any specific attestation data encoded according to the schema.
    • digitalAssetAddress: The contract address of the asset being attested.
    • digitalAssetId: The token ID of the asset.
    • grade: The numerical grade (0-3) chosen by the owner, influencing bond requirements and Level 2 thresholds.
    • bondAmount: The amount of bond tokens required for the chosen grade’s fee.
  5. Contract Execution (CAS):
    • The CASContract verifies the inputs (schema exists, grade is valid, etc.).
    • It transfers the bondAmount from the owner to itself using the prior approval.
    • It deploys a new RightsBoundAccount contract specifically for this asset’s attestation, linking the asset to its future royalty and rights management.
    • It stores the attestation details (UID, schema, recipient, attester, times, data, asset link, RBA link).
    • It updates the asset’s level to 1 (digitalAssetLevels mapping).
    • It emits an Attested event (containing the attestationUID) and a CulturaRightsBoundAccountDeployed event (containing the RBA address).
  6. Result (SDK/Dapp): The SDK parses the events from the transaction receipt using getAttestationDataFromReceipt to extract the attestationUID and the rightsBoundAccount address, returning them to the dApp. The asset is now Level 1 (Attested).

Part 2: Verification (Level 1 -> Level 2)

  1. Verifier Initiates: A community member or whitelisted verifier uses a dApp to verify/endorse an existing Level 1 attestation, identified by its attestationUID.
  2. Bond Token Approval (SDK): The SDK prompts the verifier’s wallet to approve the CASContract to spend the verificationBond amount they wish to contribute. This calls sdk.bondToken.approve().
  3. Verification Call (SDK): The SDK calls sdk.attestationService.verifyAttestation(), providing the attestationUID and the verificationBond amount.
  4. Contract Execution (CAS):
    • The CASContract verifies the attestation exists and is currently Level 1.
    • It transfers the verificationBond from the verifier to itself.
    • It records the verification details, linking the verifier and their bonded amount to the attestation (verifierCount, communityUserCount, WLVerifierCount, bondedAmount on the Attestation struct are updated).
    • It checks if the promotion thresholds (total bonded tokens OR number/type of unique verifiers) defined by the asset’s Grade have now been met by calling the internal _checkVerifiedVerifiedLevel function.
    • If thresholds are met: The contract updates the asset’s level to 2 (digitalAssetLevels mapping) via _promoteDigitalAsset.
    • It emits a Verified event.
  5. Result (SDK/Dapp): The SDK returns the transaction result. If the thresholds were met during this verification, the asset is now Level 2 (Verified Rights) and is considered licensable through integrated protocols.

SDK Snippets

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

/**
 * Attests an asset, moving it from Level 0 to Level 1.
 * Assumes the SDK is initialized for the ASSET OWNER.
 */
async function attestAsset(
  sdk: CulturaSDK,
  recipientAddress: `0x${string}`, // Usually the owner's address
  assetAddress: `0x${string}`,
  assetId: bigint,
  grade: bigint, // e.g., 3n for Grade C
  bondAmount: bigint // e.g., parseEther("0.0001") for Grade C bond fee
) {
  try {
    // --- 1. Ensure Schema Exists ---
    // Define the schema string used for your attestations
    const schemaStr =
      "string digitalAssetName, string digitalAssetDescription, uint256 grade"; // Example
    const isRevocable = true; // Or false, depending on your use case
    const schemaUID = await sdk.schemaRegistry.getOrRegisterSchema(
      schemaStr,
      isRevocable
    );
    console.log("Using Schema UID:", schemaUID);

    // --- 2. Approve Bond Token Spending ---
    const casAddress = sdk.config.contracts?.attestationService;
    if (!casAddress) throw new Error("CAS address not configured in SDK");

    console.log(
      `Approving ${bondAmount.toString()} bond tokens for CAS (${casAddress})...`
    );
    // Ensure the owner has sufficient bond tokens before approving
    const approveTxHash = await sdk.bondToken.approve(casAddress, bondAmount);
    console.log("Bond Token Approval Tx:", approveTxHash);
    // It's crucial to wait for this transaction to be mined before proceeding
    await sdk.publicClient.waitForTransactionReceipt({ hash: approveTxHash });
    console.log("Approval confirmed.");

    // --- 3. Prepare Attestation Request ---
    // Encode data according to your schema if necessary. For the example schema,
    // you might encode the asset name, description, and grade again here,
    // or leave data as '0x' if the core info is implicitly linked via assetId.
    const encodedData = "0x" as `0x${string}`; // Placeholder

    const attestationRequest = {
      schema: schemaUID,
      data: {
        recipient: recipientAddress,
        expirationTime: 0n, // 0 for non-expiring attestations
        revocable: isRevocable,
        refUID:
          "0x0000000000000000000000000000000000000000000000000000000000000000" as `0x${string}`, // Use if referencing another attestation
        data: encodedData,
      },
    };

    // --- 4. Call Attest ---
    console.log("Submitting attestation to CAS...");
    // The attest method waits for the transaction and returns the UID directly
    const attestationUid = await sdk.attestationService.attest(
      attestationRequest,
      assetAddress,
      assetId,
      grade,
      bondAmount // This is the fee/initial bond required by the grade
    );
    console.log(`Attestation Successful! UID: ${attestationUid}`);
    console.log(`Asset is now Level 1 (Attested)`);

    // Note: The RightsBoundAccount is deployed automatically during attest.
    // You can query for it later using the attestation UID or asset details if needed.
    // For simplicity, we're not retrieving it explicitly here.

    return { attestationUid }; // Return only the UID for this example
  } catch (error) {
    console.error("Error during attestation:", error);
    throw error; // Re-throw for higher-level handling
  }
}

/**
 * Verifies an existing Level 1 attestation, potentially promoting it to Level 2.
 * Assumes the SDK is initialized for the VERIFIER.
 */
async function verifyAssetAttestation(
  sdk: CulturaSDK, // SDK instance initialized for the VERIFIER
  attestationUID: `0x${string}`,
  verificationBond: bigint // Amount the verifier wants to bond, e.g., parseEther("0.01")
) {
  try {
    // --- 1. Approve Bond Token Spending ---
    const casAddress = sdk.config.contracts?.attestationService;
    if (!casAddress) throw new Error("CAS address not configured in SDK");

    console.log(
      `Approving ${verificationBond.toString()} tokens for CAS (${casAddress})...`
    );
    // Ensure the verifier has sufficient bond tokens
    const approveTxHash = await sdk.bondToken.approve(
      casAddress,
      verificationBond
    );
    console.log("Approval Tx:", approveTxHash);
    await sdk.publicClient.waitForTransactionReceipt({ hash: approveTxHash });
    console.log("Approval confirmed.");

    // --- 2. Call Verify Attestation ---
    console.log(`Verifying attestation ${attestationUID}...`);
    const verifyTxHash = await sdk.attestationService.verifyAttestation(
      attestationUID,
      verificationBond
    );
    console.log("Verify Tx:", verifyTxHash);

    const receipt = await sdk.publicClient.waitForTransactionReceipt({
      hash: verifyTxHash,
    });
    if (receipt.status !== "success") {
      throw new Error("Verification transaction failed");
    }

    console.log(`Verification submitted successfully.`);

    // --- 3. Check the new level (Optional - requires querying state) ---
    // This requires knowing the assetAddress and assetId associated with the UID.
    // You'd typically fetch this using the attestationUID via the SDK query client or getAttestation.
    // Example:
    // const attestationData = await sdk.attestationService.getAttestation(attestationUID);
    // if (attestationData) {
    //   const newLevel = await sdk.attestationService.getDigitalAssetLevel(
    //     attestationData.digitalAssetAddress,
    //     attestationData.digitalAssetId
    //   );
    //   console.log(`Asset level is now: ${newLevel}`);
    //   if (newLevel === 2) {
    //     console.log("Asset promoted to Level 2 (Verified Rights / Licensable)!");
    //   }
    // }

    return verifyTxHash;
  } catch (error) {
    console.error("Error during verification:", error);
    throw error; // Re-throw for higher-level handling
  }
}