Skip to main content
Reading data efficiently from the blockchain is crucial for dApps. Cultura utilizes The Graph protocol for indexing blockchain data, making it easily queryable via GraphQL. The Cultura SDK provides a convenient QueryClient to interact with this indexed data, abstracting away the GraphQL specifics.

Flow Diagram

Explanation

  1. User Request: The user interacts with the dApp, triggering a need for asset or rights data (e.g., viewing a gallery, checking an asset’s details).
  2. Dapp Calls SDK: The dApp uses the initialized Cultura SDK instance to call a specific query method on sdk.query.
    • For asset data: sdk.query.digitalAsset.getById(tokenId), getByOwner(ownerAddress), etc.
    • For verification status: sdk.query.verifiedRights.getByDigitalAsset(assetAddress, assetId), getByOwner(ownerAddress), etc.
  3. Query Execution: The SDK’s query client constructs the appropriate GraphQL query based on the method called and its parameters.
  4. Subgraph Interaction: The query client sends the GraphQL query to the relevant Cultura subgraph endpoint (determined by the SDK’s configured environment - local, devnet, testnet).
  5. Data Retrieval: The subgraph processes the query against its indexed blockchain data and returns the results matching the query filters and structure.
  6. Response: The query client receives the data and returns it to the dApp, already parsed into the TypeScript types defined in the SDK (e.g., DigitalAsset, VerifiedRights).
  7. Display: The dApp uses the retrieved structured data to update the UI.

Key Considerations

  • Performance: Querying the subgraph is significantly faster and cheaper than reading data directly from the blockchain contracts.
  • Data Availability: Subgraphs index blockchain events. There might be a slight delay (seconds to minutes) between a transaction occurring on-chain and the data becoming available in the subgraph. Design your UI to handle potential loading states or eventual consistency.
  • Read-Only: The query client provides read-only access to data. For actions that modify the blockchain state (minting, attesting, verifying, transferring), you need an SDK instance connected to a wallet.

SDK Snippet Examples

import { CulturaSDK } from "@cultura/sdk";
import { getAddress } from "viem"; // Utility for checksumming addresses

async function queryExamples(sdk: CulturaSDK, userAddress: `0x${string}`) {
  const queryClient = sdk.query;
  // Assume the primary asset contract address is configured in the SDK
  const assetContractAddress = sdk.config.contracts?.culturaDigitalAsset;

  if (!assetContractAddress) {
    console.error(
      "CulturaDigitalAsset contract address not configured in SDK."
    );
    return;
  }

  try {
    // --- Example 1: Get Assets Owned by User ---
    console.log(`--- Querying Assets Owned by ${userAddress} ---`);
    const ownedAssets = await queryClient.digitalAsset.getByOwner(
      userAddress,
      10, // Limit to 10 results
      0 // Offset 0 (first page)
    );
    console.log(`Found ${ownedAssets.length} assets.`);
    ownedAssets.forEach((asset) => {
      console.log(
        `- ID: ${asset.tokenId}, Name: ${
          asset.assetName || "N/A"
        }, Is Licensed Creative Asset: ${asset.isLicensedAsset}`
      );
      // Access nested verified rights info if available directly from the asset query
      if (asset.verifiedRights) {
        console.log(
          `  Verified Rights Grade: ${asset.verifiedRights.grade}, Level 2: ${asset.verifiedRights.isVerified}`
        );
      }
    });

    // --- Example 2: Get Verified Rights Info for a Specific Asset ---
    if (ownedAssets.length > 0) {
      const firstAssetId = ownedAssets[0].tokenId; // Subgraph uses string IDs
      console.log(
        `\n--- Querying Verified Rights for Asset ID ${firstAssetId} ---`
      );
      const rightsForAsset = await queryClient.verifiedRights.getByDigitalAsset(
        assetContractAddress,
        firstAssetId
      );
      if (rightsForAsset) {
        console.log(`  Found Verified Rights Entry:`);
        console.log(`  Attestation UID: ${rightsForAsset.attestationUID}`);
        console.log(`  Grade: ${rightsForAsset.grade}`);
        console.log(`  Is Verified (Level 2): ${rightsForAsset.isVerified}`);
        console.log(
          `  Current Bond: ${rightsForAsset.currentBondAmount} (wei)`
        );
        console.log(`  Total Verifiers: ${rightsForAsset.attestorCount}`); // Field name might be attestorCount
        console.log(
          `  WL Verifiers: ${rightsForAsset.whitelistedAttesterCount}`
        );
        console.log(
          `  Community Verifiers: ${rightsForAsset.communityAttesterCount}`
        );
      } else {
        console.log(
          "  No verified rights entry found (Asset might be Level 0 or attestation failed)."
        );
      }
    }

    // --- Example 3: Get All Assets that Reached Level 2 ---
    console.log(`\n--- Querying All Level 2 Verified Assets (First 5) ---`);
    const verifiedLevel2Rights = await queryClient.verifiedRights.getVerified(
      5, // Limit
      0 // Offset
    );
    console.log(
      `Found ${verifiedLevel2Rights.length} assets with Level 2 Verified Rights.`
    );
    verifiedLevel2Rights.forEach((vr) => {
      console.log(
        `- Asset ID: ${vr.digitalAssetId} (Contract: ${vr.digitalAssetAddress}), Grade: ${vr.grade}`
      );
      // Access linked asset details (name, etc.) if needed:
      // console.log(`  Asset Name: ${vr.digitalAsset?.assetName}`);
    });

    // --- Example 4: Get Licensed Assets derived from a Parent ---
    const parentAssetIdToQuery = "1"; // Example parent ID (as string for subgraph query)
    console.log(
      `\n--- Querying Licensed Assets derived from Parent ID ${parentAssetIdToQuery} ---`
    );
    const licensedChildren = await queryClient.digitalAsset.getLicensedAssets(
      parentAssetIdToQuery,
      assetContractAddress, // Assuming parent is also in this contract
      10,
      0
    );
    console.log(`Found ${licensedChildren.length} licensed children.`);
    licensedChildren.forEach((child) => {
      console.log(
        `- Child ID: ${child.tokenId}, Name: ${child.assetName || "N/A"}`
      );
      // You can inspect child.parentAssets array here if needed
      // child.parentAssets?.forEach(p => console.log(`  Parent: ${p.parentDigitalAssetId}, Split: ${p.royaltySplit}`));
    });
  } catch (error) {
    console.error("Error during query examples:", error);
  }
}

// --- Usage ---
// Assuming 'sdk' is an initialized CulturaSDK instance
// const sdk = CulturaSDK.create({ chain: 'testnet' }); // Read-only is fine for queries
// const someUserAddress = getAddress('0x...'); // Replace with checksummed address
// queryExamples(sdk, someUserAddress);

// --- Detailed Examples from SDK Reference Quickstart ---

// Example 1: Get all verified rights
async function getVerifiedRights(sdk: CulturaSDK) {
  const queryClient = sdk.query;
  const verifiedRights = await queryClient.verifiedRights.getAll(10, 0);
  console.log(`Found ${verifiedRights.length} verified rights:`);
  verifiedRights.forEach((right) => {
    console.log(`- ID: ${right.id}, Grade: ${right.grade}`);
    console.log(`  Asset Name: ${right.digitalAsset?.assetName || "Unnamed"}`);
    console.log(`  Bond Amount: ${right.currentBondAmount}`);
    console.log(`  Is Verified (Level 2): ${right.isVerified}`);
  });
}

// Example 2: Get all digital assets
async function getDigitalAssets(sdk: CulturaSDK) {
  const queryClient = sdk.query;
  const assets = await queryClient.digitalAsset.getAll(10, 0);
  console.log(`Found ${assets.length} digital assets:`);
  assets.forEach((asset) => {
    console.log(`- Token ID: ${asset.tokenId}`);
    console.log(`  Owner: ${asset.owner.address}`);
    console.log(`  Name: ${asset.assetName || "Unnamed"}`);
    console.log(`  Licensed Asset: ${asset.isLicensedAsset ? "Yes" : "No"}`);
  });
}

// Example 3: Get verified rights for a specific owner
async function getRightsByOwner(sdk: CulturaSDK, ownerAddress: `0x${string}`) {
  const queryClient = sdk.query;
  const ownerRights = await queryClient.verifiedRights.getByOwner(ownerAddress);
  console.log(`Found ${ownerRights.length} rights for owner ${ownerAddress}:`);
  ownerRights.forEach((right) => {
    console.log(`- ID: ${right.id}, Grade: ${right.grade}`);
    if (right.digitalAsset) {
      console.log(`  Asset: ${right.digitalAsset.assetName || "Unnamed"}`);
    }
  });
}

// Example 4: Get licensed assets derived from a parent asset
async function getLicensedAssets(
  sdk: CulturaSDK,
  parentAssetId: string,
  contractAddress: `0x${string}`
) {
  const queryClient = sdk.query;
  const licensedAssets = await queryClient.digitalAsset.getLicensedAssets(
    parentAssetId,
    contractAddress
  );
  console.log(
    `Found ${licensedAssets.length} licensed assets derived from parent ${parentAssetId}:`
  );
  licensedAssets.forEach((asset) => {
    console.log(`- Token ID: ${asset.tokenId}`);
    console.log(`  Owner: ${asset.owner.address}`);
    console.log(`  Name: ${asset.assetName || "Unnamed"}`);
  });
}

// --- How to Run These Examples ---
// async function runAllQueries(sdk: CulturaSDK) {
//   const exampleOwner = getAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"); // Example address
//   const exampleParentId = "1"; // Example parent token ID
//   const exampleContract = sdk.config.contracts?.culturaDigitalAsset;

//   if (!exampleContract) {
//     console.error("Asset contract address not found in SDK config.");
//     return;
//   }

//   try {
//     console.log("\n--- Running getVerifiedRights ---");
//     await getVerifiedRights(sdk);
//     console.log("\n--- Running getDigitalAssets ---");
//     await getDigitalAssets(sdk);
//     console.log("\n--- Running getRightsByOwner ---");
//     await getRightsByOwner(sdk, exampleOwner);
//     console.log("\n--- Running getLicensedAssets ---");
//     await getLicensedAssets(sdk, exampleParentId, exampleContract);
//   } catch (error) {
//     console.error("Error running query examples:", error);
//   }
// }

// const readOnlySdk = CulturaSDK.create({ chain: 'testnet' });
// runAllQueries(readOnlySdk);