Skip to main content

Product Update - Cultura SDK v0.2.0 Released

This release implements a new streamlined royalty claiming process for off-chain payments and UX improvements to make implementation and sponsorship easier. The parent asset structure has been refactored to use Rights Bound Account addresses, simplifying derivative asset creation. Enhanced query capabilities provide richer data, and new utility functions improve developer experience. This release represents a major update to the Cultura SDK with significant architectural improvements, new features, and some breaking changes. This guide will help you migrate from v0.1.10 to v0.2.0.

Major Changes & New Features

1. BREAKING CHANGE: Parent Asset Structure Refactor

The most significant change in v0.2.0 is the restructuring of parent asset handling for derivative works.

What Changed

  • v0.1.10: Used ParentDigitalAsset type with parentCollection, parentDigitalAssetId, owner, royaltySplit
  • v0.2.0: Uses ParentInfo type with rightsBoundAccount, royaltySplit

Migration Required

Old v0.1.10 Structure:
// v0.1.10 - Parent asset info included collection, tokenId, and owner
const parentAssets: ParentDigitalAsset[] = [
  {
    parentCollection: "0x123...",
    parentDigitalAssetId: 42n,
    owner: "0xabc...",
    royaltySplit: 75n,
  },
];

await sdk.culturaDigitalAsset.mintDigitalAsset(
  to,
  name,
  description,
  parentAssets, // Old structure
  termsData,
  tokenURI
);
New v0.2.0 Structure:
// v0.2.0 - Parent asset info uses Rights Bound Account address
const parentInfo: ParentInfo[] = [
  {
    rightsBoundAccount: "0xdef...", // RBA address of parent asset
    royaltySplit: 75n,
  },
];

await sdk.culturaDigitalAsset.mintDigitalAsset(
  to,
  name,
  description,
  parentInfo, // New structure
  termsData,
  tokenURI
);

How to Get Rights Bound Account Address

You can obtain the Rights Bound Account address for a parent asset in several ways:
// Method 1: Query from subgraph data
const parentAsset = await sdk.query.digitalAsset.getByContractAndTokenId(
  parentContractAddress,
  parentTokenId
);
const rightsBoundAccount = parentAsset?.rightsBoundAccount;

// Method 2: Query from verified rights data
const verifiedRights = await sdk.query.verifiedRights.getByDigitalAsset(
  parentContractAddress,
  parentTokenId.toString()
);
const rightsBoundAccount = verifiedRights?.rightsBoundAccount;

// Method 3: If you have attestation receipt from parent creation
const rightsBoundAccount =
  sdk.attestationService.getRightsBoundAccountFromReceipt(receipt);

2. Enhanced Query System with Richer Data

New Query Features

  • Enhanced Verification Data: verifiedRights queries now include detailed verifications array with individual verifier information
  • Owner Token Counts: digitalAsset.owner now includes tokenCount field
  • Nested Asset Information: Better cross-referencing between digital assets and verified rights

Example of Enhanced Data Usage

// v0.2.0 - Access richer verification data
const verifiedRights = await sdk.query.verifiedRights.getAll(10, 0);
verifiedRights.forEach((right) => {
  console.log(`Asset: ${right.digitalAsset?.assetName}`);
  console.log(`Owner Token Count: ${right.digitalAsset?.owner.tokenCount}`);

  // New: Detailed verification information
  right.verifications.forEach((verification) => {
    console.log(`Verifier: ${verification.verifier.address}`);
    console.log(`Bond Amount: ${verification.bondAmount}`);
    console.log(`Timestamp: ${verification.timestamp}`);
    console.log(`Is Whitelisted: ${verification.verifier.isWhitelisted}`);
  });
});

3. New Utility Functions

Digital Asset and Verified Rights ID Generation

import { getDigitalAssetUID, getVerifiedRightsId } from "@cultura/sdk/utils";

// Generate unique identifiers for assets and rights
const digitalAssetUID = getDigitalAssetUID(contractAddress, tokenId);
const verifiedRightsId = getVerifiedRightsId(contractAddress, tokenId);

// Use these IDs for queries
const asset = await sdk.query.digitalAsset.getByUID(digitalAssetUID);
const rights = await sdk.query.verifiedRights.getById(verifiedRightsId);

4. Royalty System UX Improvements

New Off-Chain Payment Methods (Continued from v0.1.10)

The off-chain payment workflow introduced in v0.1.10 has been refined with improved UX:
// Report off-chain payment
await sdk.royalty.reportOffChainPayment(tokenId, periodIndex, amount, metadata);

// Accept off-chain payment by parent
await sdk.royalty.acceptOffChainPaymentByParent(
  tokenId,
  periodIndex,
  parentBoundAccount,
  signature,
  signerAddress,
  sponsorAddress
);

// Deny off-chain payment by parent
await sdk.royalty.denyOffChainPaymentByParent(
  tokenId,
  periodIndex,
  parentBoundAccount,
  signature,
  signerAddress
);

New Signature Helper Methods

// Generate signatures for off-chain payment acceptance
const acceptanceSignature =
  await sdk.royalty.signForAcceptOffChainPaymentByParent(
    rightsBoundAccount,
    totalAmount,
    period,
    sponsor
  );

// Generate signatures for off-chain payment denial
const denialSignature = await sdk.royalty.signForDenyOffChainPaymentByParent(
  tokenId,
  periodIndex,
  parentBoundAccount
);

5. NEW: Sponsorship Support

v0.2.0 introduces extensive sponsorship capabilities across the entire royalty and attestation system, enabling third parties to cover transaction costs and execute operations on behalf of others.

Delegated Royalty Operations

// Delegated royalty registration - sponsor registers royalty on behalf of asset owner
const registrationSignature =
  await sdk.royalty.signForDelegatedRegisterRoyaltyDue(
    tokenId,
    amountDue,
    startDate,
    endDate,
    royaltyInfoIndex
  );
await sponsorSDK.royalty.delegatedRegisterRoyaltyDue(
  tokenId,
  amountDue,
  startDate,
  endDate,
  royaltyInfoIndex,
  registrationSignature
);

// Delegated royalty payment - sponsor pays royalty on behalf of licensee
const paymentSignature = await sdk.royalty.signForDelegatedPayRoyalty(
  tokenId,
  amountDue,
  royaltyInfoIndex
);
await sponsorSDK.royalty.delegatedPayRoyalty(
  tokenId,
  amountPaid,
  periodIndex,
  culturaBoundAccount,
  signature,
  paymentSignature
);

Delegated Off-Chain Payment Flow

// Sponsor reports off-chain payment on behalf of licensee
const reportSignature = await sdk.royalty.signForDelegatedOffchainReportRoyalty(
  tokenId,
  royaltyInfoIndex,
  amount
);
await sponsorSDK.royalty.delegatedReportOffChainPayment(
  tokenId,
  periodIndex,
  amount,
  metadata,
  reportSignature
);

// Example from licensing flow - sponsor handles all off-chain payment operations
const offchainSignature =
  await licenseeSDK.royalty.signForDelegatedRegisterRoyaltyDue(
    licensedAssetId,
    offchainRoyaltyDue,
    offchainPeriodStartDate,
    offchainPeriodEndDate,
    offchainPeriodIndex
  );
await sponsorSDK.royalty.delegatedRegisterRoyaltyDue(
  licensedAssetId,
  offchainRoyaltyDue,
  offchainPeriodStartDate,
  offchainPeriodEndDate,
  offchainPeriodIndex,
  offchainSignature
);

Rights Bound Account Sponsorship

// Sponsor sets payment info on behalf of asset owner
const paymentSignature = await sdk.rightsBoundAccount.signForSetPaymentInfo(
  period,
  totalAmount,
  sponsorAddress
);

// Sponsor SDK executes the transaction
sponsorSDK.setRightsBoundAccount(rightsBoundAccountAddress);
await sponsorSDK.rightsBoundAccount.setPaymentInfo({
  totalAmount,
  sponsor: sponsorAddress,
  signature: paymentSignature,
  signer: assetOwnerAddress, // Original signer but sponsor executes
});

Attestation Sponsorship

// Sponsored attestation creation
const { attestationUid, rightsBoundAccount } =
  await sdk.attestationService.sponsoredAttest(
    attestationRequest,
    digitalAssetAddress,
    digitalAssetId,
    classId,
    bondAmount,
    sponsorAddress
  );

// Delegated attestation with sponsor
const delegatedSignature =
  await sdk.attestationService.signForDelegatedAttestation(recipientAddress);
const { attestationUid, rightsBoundAccount } =
  await sponsorSDK.attestationService.sponsoredDelegatedAttest(
    attestationRequest,
    digitalAssetAddress,
    digitalAssetId,
    classId,
    bondAmount,
    delegatedSignature,
    recipientAddress,
    sponsorAddress
  );

Sponsorship Benefits

  • Gas-Free User Experience: Users can interact without owning native tokens
  • Platform Integration: Marketplaces can sponsor user interactions seamlessly
  • Enterprise Workflows: Complex multi-party operations with clear cost attribution
  • Accessibility: Removes technical barriers for non-crypto native users
  • Flexible Business Models: Enables subscription, freemium, and sponsored content models
  • Scalable Operations: Third parties can batch and optimize transaction execution

6. Enhanced Rights Bound Account Management

New Methods

// Get asset information associated with a Rights Bound Account
const assetInfo = await sdk.rightsBoundAccount.getRightsBoundAccountAsset();
console.log(`Asset Address: ${assetInfo.assetAddress}`);
console.log(`Token ID: ${assetInfo.tokenId}`);

// Improved payment info signing with sponsor support
const signature = await sdk.rightsBoundAccount.signForSetPaymentInfo(
  period,
  totalAmount,
  sponsor
);

7. Improved UX with Attestation

Better Developer Experience for Attestation Functions

v0.2.0 improves the developer experience by having attestation functions return the attestationUid and rightsBoundAccount directly instead of just transaction hashes. This eliminates the need for developers to manually wait for transactions and parse receipts.
// v0.2.0 - Direct returns for better UX
const { attestationUid, rightsBoundAccount } =
  await sdk.attestationService.attest(
    attestationRequest,
    digitalAssetAddress,
    digitalAssetId,
    assetClass,
    bondAmount
  );

// No need to wait for transaction or parse receipts manually
console.log(`Attestation UID: ${attestationUid}`);
console.log(`Rights Bound Account: ${rightsBoundAccount}`);

// Same improvement applies to other attestation methods:
// - sponsoredAttest()
// - delegatedAttest()
// - sponsoredDelegatedAttest()

Migration Checklist

Critical - Required Changes

1. Update Parent Asset Structure

  • Replace all ParentDigitalAsset[] with ParentInfo[]
  • Update minting calls to use Rights Bound Account addresses instead of collection/tokenId/owner
  • Implement Rights Bound Account address lookup for existing parent assets

2. Update Import Statements

// Add new utility imports if needed
import { getDigitalAssetUID, getVerifiedRightsId } from "@cultura/sdk/utils";

3. Leverage Enhanced Query Data

  • Update query result handling to use new verifications array data
  • Use owner.tokenCount information where relevant
  • Implement richer verification displays using detailed verifier information

4. Implement New Utility Functions

  • Use getDigitalAssetUID and getVerifiedRightsId for consistent ID generation
  • Replace manual ID generation with utility functions

5. Enhanced Error Handling

  • Update error handling for new Rights Bound Account requirements
  • Add validation for Rights Bound Account address existence

Example Migration

Before (v0.1.10)

// v0.1.10 minting with parent assets
const parentAssets: ParentDigitalAsset[] = [
  {
    parentCollection: "0x123...",
    parentDigitalAssetId: 42n,
    owner: "0xabc...",
    royaltySplit: 75n,
  },
];

const tokenId = await sdk.culturaDigitalAsset.mintDigitalAsset(
  recipientAddress,
  "Derivative Work",
  "Based on parent asset",
  parentAssets,
  termsData,
  tokenURI
);

After (v0.2.0)

// v0.2.0 minting with parent info
// First, get the Rights Bound Account address for the parent
const parentAsset = await sdk.query.digitalAsset.getByContractAndTokenId(
  "0x123...", // parent collection
  42n // parent token ID
);

if (!parentAsset?.rightsBoundAccount) {
  throw new Error("Parent asset Rights Bound Account not found");
}

const parentInfo: ParentInfo[] = [
  {
    rightsBoundAccount: parentAsset.rightsBoundAccount,
    royaltySplit: 75n,
  },
];

const tokenId = await sdk.culturaDigitalAsset.mintDigitalAsset(
  recipientAddress,
  "Derivative Work",
  "Based on parent asset",
  parentInfo, // Updated structure
  termsData,
  tokenURI
);

Common Migration Issues

Issue 1: Rights Bound Account Not Found

Problem: Parent asset doesn’t have a Rights Bound Account address Solution: Ensure the parent asset has been properly attested and verified
// Check if parent asset has been attested
const verifiedRights = await sdk.query.verifiedRights.getByDigitalAsset(
  parentCollection,
  parentTokenId.toString()
);

if (!verifiedRights) {
  throw new Error("Parent asset must be attested before use in derivatives");
}

Issue 2: Type Errors with Parent Info

Problem: TypeScript errors when switching from ParentDigitalAsset to ParentInfo Solution: Update type imports and structure
// Update imports
import type { ParentInfo } from "@cultura/sdk";

// Use correct type
const parentInfo: ParentInfo[] = [
  /* ... */
];

Testing Your Migration

  1. Test Parent Asset Queries: Ensure you can successfully retrieve Rights Bound Account addresses
  2. Test Minting: Verify derivative asset minting works with new parent info structure
  3. Test Enhanced Queries: Confirm enhanced query data is accessible
  4. Test Royalty Flows: Verify royalty distribution still works correctly

Getting Help

If you encounter issues during migration: The v0.2.0 update provides a more robust and feature-rich SDK while maintaining backward compatibility where possible. The main breaking change around parent asset structure enables better royalty distribution and cleaner architecture going forward.