> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cultura.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Royalty Payments Flow

This flow outlines how royalties are typically reported, paid, and claimed for **Licensed Creative Assets** within the Cultura ecosystem, utilizing the reference `RoyaltyClient` and associated contracts like `RightsBoundAccount` and `VerifierModule`. The v0.2.0 SDK introduces enhanced sponsorship capabilities and streamlined off-chain payment flows.

## Prerequisites

* A **Licensed Creative Asset** exists (Token ID: `licensedAssetId`, Contract: `assetContractAddress`), minted with parent asset information (including `royaltySplit`).
* The asset has an associated **Attestation** and a deployed **Rights Bound Account (RBA)** (Address: `rbaAddress`).
* The **Licensee** (owner/user of the Licensed Creative Asset) has generated revenue or usage that triggers a royalty payment obligation (`totalRoyaltyAmount`).
* The **Parent Asset Owner(s)** and **Verifiers** (who endorsed the Licensed Creative Asset's attestation) have wallets ready to claim.
* The designated **Bond/Payment Token** contract address is known and configured in the SDK.
* SDK instances are initialized for the Licensee, Parent Owner(s), Verifiers, and optionally a **Sponsor** for gas-free operations.

## Payment Methods

The Cultura ecosystem supports two primary royalty payment methods:

### 1. On-Chain Payment Flow

Direct blockchain-based payments where tokens are transferred through smart contracts.

### 2. Off-Chain Payment Flow (v0.2.0+)

Traditional payment methods (bank transfers, credit cards) with on-chain acceptance/denial by rights holders.

## On-Chain Payment Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant Licensee
    participant Dapp
    participant SDK_Licensee as SDK (Licensee)
    participant RoyaltyModule
    participant BondTokenContract
    participant VR_RBA as VR RightsBoundAccount
    participant Parent1_RBA as Parent1 RBA
    participant Parent2_RBA as Parent2 RBA
    participant ParentOwner
    participant SDK_Parent as SDK (Parent)
    participant Verifier
    participant SDK_Verifier as SDK (Verifier)
    participant VerifierModule
    participant Sponsor
    participant SDK_Sponsor as SDK (Sponsor)

    %% Optional Reporting Step
    Licensee->>Dapp: Report Usage/Revenue for Period
    Dapp->>SDK_Licensee: royalty.registerRoyaltyDue(licensedAssetId, totalRoyaltyAmount, start, end, periodIdx)
    activate SDK_Licensee
    SDK_Licensee->>RoyaltyModule: registerRoyaltyDue(...)
    RoyaltyModule-->>SDK_Licensee: Success (Event Emitted)
    deactivate SDK_Licensee
    Note over Dapp, SDK_Licensee: Step 1 (Optional): Register Royalty Due

    %% Sponsored Payment Step
    Sponsor->>Dapp: Sponsor Payment for Licensee
    Dapp->>SDK_Sponsor: bondToken.approve(vrRbaAddress, totalRoyaltyAmount)
    activate SDK_Sponsor
    SDK_Sponsor->>BondTokenContract: approve(vrRbaAddress, totalRoyaltyAmount)
    BondTokenContract-->>SDK_Sponsor: Approval Success
    deactivate SDK_Sponsor
    Note over Dapp, SDK_Sponsor: Step 2a: Sponsor Approves VR RBA

    Dapp->>SDK_Licensee: rightsBoundAccount.signForSetPaymentInfo(periodIdx, totalAmount, sponsorAddress)
    activate SDK_Licensee
    SDK_Licensee-->>Dapp: Payment Signature
    deactivate SDK_Licensee

    Dapp->>SDK_Sponsor: rightsBoundAccount.setPaymentInfo({totalAmount, sponsor, signature, signer})
    activate SDK_Sponsor
    SDK_Sponsor->>VR_RBA: setPaymentInfo(...)
    activate VR_RBA
    VR_RBA->>BondTokenContract: transferFrom(Sponsor, VR_RBA, totalRoyaltyAmount)
    VR_RBA->>VR_RBA: Store payment splits for periodIdx
    VR_RBA-->>SDK_Sponsor: Success (PaymentInfoSet Event)
    deactivate VR_RBA
    deactivate SDK_Sponsor
    Note over Dapp, SDK_Sponsor: Step 2b: Sponsor Executes Payment to VR RBA

    %% Parent Claims from Child (VR RBA)
    ParentOwner->>Dapp: Trigger Parent Claims from Child
    Dapp->>SDK_Parent: Set Parent RBA: setRightsBoundAccount(parent1RbaAddress)
    Dapp->>SDK_Parent: rightsBoundAccount.claimFromChild(vrRbaAddress, periodIdx)
    activate SDK_Parent
    SDK_Parent->>Parent1_RBA: claimFromChild(vrRbaAddress, periodIdx)
    activate Parent1_RBA
    Parent1_RBA->>VR_RBA: Pull parent share for periodIdx
    VR_RBA->>BondTokenContract: transfer(Parent1_RBA, parent1Share)
    Parent1_RBA-->>SDK_Parent: Claim Success
    deactivate Parent1_RBA
    deactivate SDK_Parent
    Note over Dapp, SDK_Parent: Step 3a: Parent 1 Claims from VR RBA

    Dapp->>SDK_Parent: Set Parent RBA: setRightsBoundAccount(parent2RbaAddress)
    Dapp->>SDK_Parent: rightsBoundAccount.claimFromChild(vrRbaAddress, periodIdx)
    activate SDK_Parent
    SDK_Parent->>Parent2_RBA: claimFromChild(vrRbaAddress, periodIdx)
    activate Parent2_RBA
    Parent2_RBA->>VR_RBA: Pull parent share for periodIdx
    VR_RBA->>BondTokenContract: transfer(Parent2_RBA, parent2Share)
    Parent2_RBA-->>SDK_Parent: Claim Success
    deactivate Parent2_RBA
    deactivate SDK_Parent
    Note over Dapp, SDK_Parent: Step 3b: Parent 2 Claims from VR RBA

    %% Parent Owner Claims from Parent RBAs
    ParentOwner->>Dapp: Claim My Share from Parent RBAs
    Dapp->>SDK_Parent: Set Parent RBA: setRightsBoundAccount(parent1RbaAddress)
    Dapp->>SDK_Parent: rightsBoundAccount.claim(parentPeriodIdx)
    activate SDK_Parent
    SDK_Parent->>Parent1_RBA: claim(parentPeriodIdx)
    activate Parent1_RBA
    Parent1_RBA->>BondTokenContract: transfer(ParentOwner, ownerShare)
    Parent1_RBA-->>SDK_Parent: Claim Success
    deactivate Parent1_RBA
    deactivate SDK_Parent

    Dapp->>SDK_Parent: Set Parent RBA: setRightsBoundAccount(parent2RbaAddress)
    Dapp->>SDK_Parent: rightsBoundAccount.claim(parentPeriodIdx)
    activate SDK_Parent
    SDK_Parent->>Parent2_RBA: claim(parentPeriodIdx)
    activate Parent2_RBA
    Parent2_RBA->>BondTokenContract: transfer(ParentOwner, ownerShare)
    Parent2_RBA-->>SDK_Parent: Claim Success
    deactivate Parent2_RBA
    deactivate SDK_Parent
    Note over Dapp, SDK_Parent: Step 4: Parent Owner Claims from Parent RBAs

    %% Verifier Rewards Distribution
    Verifier->>Dapp: Distribute Verifier Rewards
    Dapp->>SDK_Verifier: verifierModule.distributeRewards(assetContract, parentId1, parent1RBA, parentPeriodIdx)
    activate SDK_Verifier
    SDK_Verifier->>VerifierModule: distributeRewards(...)
    activate VerifierModule
    VerifierModule->>Parent1_RBA: claimForVerifierModule(parentPeriodIdx)
    activate Parent1_RBA
    Parent1_RBA->>BondTokenContract: transfer(VerifierModule, verifierShare)
    Parent1_RBA-->>VerifierModule: Success
    deactivate Parent1_RBA
    VerifierModule-->>SDK_Verifier: Distribution Success
    deactivate VerifierModule
    deactivate SDK_Verifier

    Dapp->>SDK_Verifier: verifierModule.distributeRewards(assetContract, parentId2, parent2RBA, parentPeriodIdx)
    activate SDK_Verifier
    SDK_Verifier->>VerifierModule: distributeRewards(...)
    activate VerifierModule
    VerifierModule->>Parent2_RBA: claimForVerifierModule(parentPeriodIdx)
    activate Parent2_RBA
    Parent2_RBA->>BondTokenContract: transfer(VerifierModule, verifierShare)
    Parent2_RBA-->>VerifierModule: Success
    deactivate Parent2_RBA
    VerifierModule-->>SDK_Verifier: Distribution Success
    deactivate VerifierModule
    deactivate SDK_Verifier
    Note over Dapp, SDK_Verifier: Step 5: Distribute Rewards from Parent RBAs to VerifierModule

    %% Individual Verifier Claims
    Verifier->>Dapp: Claim My Verifier Rewards
    Dapp->>SDK_Verifier: verifierModule.claimRewards(assetContract, parentId1)
    activate SDK_Verifier
    SDK_Verifier->>VerifierModule: claimRewards(...)
    activate VerifierModule
    VerifierModule->>BondTokenContract: transfer(Verifier, verifierShare)
    VerifierModule-->>SDK_Verifier: Claim Success
    deactivate VerifierModule
    deactivate SDK_Verifier

    Dapp->>SDK_Verifier: verifierModule.claimRewards(assetContract, parentId2)
    activate SDK_Verifier
    SDK_Verifier->>VerifierModule: claimRewards(...)
    activate VerifierModule
    VerifierModule->>BondTokenContract: transfer(Verifier, verifierShare)
    VerifierModule-->>SDK_Verifier: Claim Success
    deactivate VerifierModule
    deactivate SDK_Verifier
    Note over Dapp, SDK_Verifier: Step 6: Individual Verifier Claims from VerifierModule

```

## Off-Chain Payment Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant Licensee
    participant Dapp
    participant SDK_Licensee as SDK (Licensee)
    participant RoyaltyModule
    participant Parent1_RBA as Parent1 RBA
    participant Parent2_RBA as Parent2 RBA
    participant ParentOwner
    participant SDK_Parent as SDK (Parent)
    participant Sponsor
    participant SDK_Sponsor as SDK (Sponsor)
    participant BondTokenContract

    %% Register Royalty Period
    Licensee->>Dapp: Register Off-Chain Royalty Period
    Dapp->>SDK_Licensee: royalty.signForDelegatedRegisterRoyaltyDue(...)
    activate SDK_Licensee
    SDK_Licensee-->>Dapp: Registration Signature
    deactivate SDK_Licensee

    Dapp->>SDK_Sponsor: royalty.delegatedRegisterRoyaltyDue(..., signature)
    activate SDK_Sponsor
    SDK_Sponsor->>RoyaltyModule: delegatedRegisterRoyaltyDue(...)
    RoyaltyModule-->>SDK_Sponsor: Period Registered
    deactivate SDK_Sponsor
    Note over Dapp, SDK_Sponsor: Step 1: Sponsor Registers Royalty Period

    %% Report Off-Chain Payment
    Licensee->>Dapp: Report Off-Chain Payment (Bank Transfer, etc.)
    Dapp->>SDK_Licensee: royalty.signForDelegatedOffchainReportRoyalty(...)
    activate SDK_Licensee
    SDK_Licensee-->>Dapp: Report Signature
    deactivate SDK_Licensee

    Dapp->>SDK_Sponsor: royalty.delegatedReportOffChainPayment(..., signature)
    activate SDK_Sponsor
    SDK_Sponsor->>RoyaltyModule: delegatedReportOffChainPayment(...)
    RoyaltyModule-->>SDK_Sponsor: Payment Reported
    deactivate SDK_Sponsor
    Note over Dapp, SDK_Sponsor: Step 2: Sponsor Reports Off-Chain Payment

    %% Parent Acceptance Flow
    ParentOwner->>Dapp: Accept Off-Chain Payment
    Dapp->>SDK_Parent: royalty.signForAcceptOffChainPaymentByParent(parent1RBA, amount, period, sponsor)
    activate SDK_Parent
    SDK_Parent-->>Dapp: Acceptance Signature
    deactivate SDK_Parent

    Dapp->>SDK_Sponsor: royalty.acceptOffChainPaymentByParent(..., signature, parentOwner, sponsor)
    activate SDK_Sponsor
    SDK_Sponsor->>RoyaltyModule: acceptOffChainPaymentByParent(...)
    activate RoyaltyModule
    RoyaltyModule->>Parent1_RBA: setPaymentInfo(...) [with fees from sponsor]
    activate Parent1_RBA
    Parent1_RBA->>BondTokenContract: transferFrom(Sponsor, Parent1_RBA, fees)
    Parent1_RBA-->>RoyaltyModule: Payment Accepted
    deactivate Parent1_RBA
    RoyaltyModule-->>SDK_Sponsor: Acceptance Complete
    deactivate RoyaltyModule
    deactivate SDK_Sponsor
    Note over Dapp, SDK_Sponsor: Step 3a: Parent 1 Accepts (Sponsor Pays Fees)

    ParentOwner->>Dapp: Accept Off-Chain Payment (Parent 2)
    Dapp->>SDK_Parent: royalty.signForAcceptOffChainPaymentByParent(parent2RBA, amount, period, sponsor)
    activate SDK_Parent
    SDK_Parent-->>Dapp: Acceptance Signature
    deactivate SDK_Parent

    Dapp->>SDK_Sponsor: royalty.acceptOffChainPaymentByParent(..., signature, parentOwner, sponsor)
    activate SDK_Sponsor
    SDK_Sponsor->>RoyaltyModule: acceptOffChainPaymentByParent(...)
    activate RoyaltyModule
    RoyaltyModule->>Parent2_RBA: setPaymentInfo(...) [with fees from sponsor]
    activate Parent2_RBA
    Parent2_RBA->>BondTokenContract: transferFrom(Sponsor, Parent2_RBA, fees)
    Parent2_RBA-->>RoyaltyModule: Payment Accepted
    deactivate Parent2_RBA
    RoyaltyModule-->>SDK_Sponsor: Acceptance Complete
    deactivate RoyaltyModule
    deactivate SDK_Sponsor
    Note over Dapp, SDK_Sponsor: Step 3b: Parent 2 Accepts (Sponsor Pays Fees)

    %% Alternative: Parent Denial
    alt Parent Denies Payment
        ParentOwner->>Dapp: Deny Off-Chain Payment
        Dapp->>SDK_Parent: royalty.signForDenyOffChainPaymentByParent(tokenId, period, parentRBA)
        activate SDK_Parent
        SDK_Parent-->>Dapp: Denial Signature
        deactivate SDK_Parent

        Dapp->>SDK_Sponsor: royalty.denyOffChainPaymentByParent(..., signature, parentOwner)
        activate SDK_Sponsor
        SDK_Sponsor->>RoyaltyModule: denyOffChainPaymentByParent(...)
        RoyaltyModule-->>SDK_Sponsor: Payment Denied
        deactivate SDK_Sponsor
        Note over Dapp, SDK_Sponsor: Alternative: Parent Denies Payment
    end

```

## On-Chain Payment Flow Explanation

### 1. Register Royalty Due (Optional)

* The Licensee reports usage/revenue via the dApp for a specific period
* Can be done directly or through a sponsor using `delegatedRegisterRoyaltyDue()`
* Emits a `RoyaltyDueRegistered` event for tracking

### 2. Sponsored Payment Execution

* **v0.2.0 Enhancement**: Sponsors can cover transaction costs and execute payments
* Sponsor approves the VR (Verified Rights) RBA to spend payment tokens
* Licensee signs payment authorization with sponsor address
* Sponsor executes `setPaymentInfo()` on the VR RBA, transferring funds and recording splits

### 3. Parent Claims from Child RBA

* **New Multi-Level Architecture**: Parent RBAs claim their allocated shares from the child VR RBA
* Each parent RBA calls `claimFromChild()` to pull their portion from the VR RBA
* This creates a new payment period in each parent RBA

### 4. Parent Owner Claims

* Parent owners claim their shares from their respective parent RBAs
* Uses the standard `claim()` method on each parent RBA
* Parent owners receive their allocated royalty shares

### 5. Verifier Rewards Distribution

* **Two-Step Process**:
  * First, trigger distribution from each parent RBA to the VerifierModule
  * Then, individual verifiers claim from the VerifierModule
* Verifiers earn rewards based on their proportional bond contributions
* Rewards are distributed separately for each parent asset

## Off-Chain Payment Flow Explanation

### 1. Register Off-Chain Royalty Period

* **Delegated Registration**: Sponsor can register royalty periods on behalf of licensee
* Licensee signs authorization, sponsor executes transaction
* Establishes the payment period and expected amounts

### 2. Report Off-Chain Payment

* **Traditional Payment Integration**: Support for bank transfers, credit cards, etc.
* Licensee reports payment with metadata (reference numbers, dates, notes)
* Sponsor can execute the reporting transaction

### 3. Parent Acceptance/Denial

* **Democratic Process**: Each parent owner must individually accept or deny the payment
* **Sponsored Fees**: Sponsors can cover the blockchain fees for acceptance
* **Flexible Responses**: Parents can accept their portion or deny if payment is disputed
* **Metadata Tracking**: Full audit trail of off-chain payment details

## SDK Snippets

### On-Chain Payment Flow

```typescript theme={null}
import { CulturaSDK, ParentInfo } from "@cultura/sdk";
import { parseEther, getAddress } from "viem";

// --- Step 1: Register Royalty Due (Optional) ---
async function registerDue(sdk: CulturaSDK) {
  try {
    const startDate = BigInt(Math.floor(Date.now() / 1000));
    const endDate = startDate + 30n * 24n * 60n * 60n; // 30 days later
    console.log(`Registering royalty due for period ${periodIndex}...`);
    const txHash = await sdk.royalty.registerRoyaltyDue(
      licensedAssetId,
      totalRoyaltyAmount,
      startDate,
      endDate,
      periodIndex
    );
    await sdk.publicClient.waitForTransactionReceipt({ hash: txHash });
    console.log("Royalty due registered:", txHash);
  } catch (e) {
    console.error("Error registering royalty due:", e);
  }
}

// --- Step 2: Sponsored Payment Flow ---
async function sponsoredPaymentFlow(
  licenseeSdk: CulturaSDK,
  sponsorSdk: CulturaSDK,
  sponsorAddress: `0x${string}`,
  licenseeAddress: `0x${string}`
) {
  try {
    // 2a: Sponsor approves VR RBA to spend tokens
    console.log(
      `Sponsor approving VR RBA ${vrRbaAddress} to spend ${totalRoyaltyAmount}...`
    );
    const approveTx = await sponsorSdk.bondToken.approve(
      vrRbaAddress,
      totalRoyaltyAmount
    );
    await sponsorSdk.publicClient.waitForTransactionReceipt({
      hash: approveTx,
    });
    console.log("VR RBA approved by sponsor.");

    // 2b: Licensee signs payment authorization with sponsor
    console.log("Licensee signing payment authorization...");
    licenseeSdk.setRightsBoundAccount(vrRbaAddress);
    const paymentSignature =
      await licenseeSdk.rightsBoundAccount.signForSetPaymentInfo(
        periodIndex,
        totalRoyaltyAmount,
        sponsorAddress
      );
    console.log("Payment signature obtained from licensee.");

    // 2c: Sponsor executes payment
    console.log("Sponsor executing payment to VR RBA...");
    sponsorSdk.setRightsBoundAccount(vrRbaAddress);
    await sponsorSdk.rightsBoundAccount.setPaymentInfo({
      totalAmount: totalRoyaltyAmount,
      sponsor: sponsorAddress,
      signature: paymentSignature,
      signer: licenseeAddress,
    });
    console.log("Payment executed successfully by sponsor.");
  } catch (e) {
    console.error("Error in sponsored payment flow:", e);
    throw e;
  }
}

// --- Step 3: Parent Claims from Child (VR RBA) ---
async function parentClaimsFromChild(
  sdk: CulturaSDK,
  parentRbaAddress: `0x${string}`
) {
  try {
    sdk.setRightsBoundAccount(parentRbaAddress);
    console.log(
      `Parent claiming from child VR RBA for period ${periodIndex}...`
    );
    const claimTx = await sdk.rightsBoundAccount.claimFromChild(
      vrRbaAddress,
      periodIndex
    );
    await sdk.publicClient.waitForTransactionReceipt({ hash: claimTx });
    console.log("Parent successfully claimed from child VR RBA.");
  } catch (e) {
    console.error("Error claiming from child:", e);
    throw e;
  }
}

// --- Step 4: Parent Owner Claims from Parent RBA ---
async function claimFromParentRba(
  sdk: CulturaSDK,
  parentRbaAddress: `0x${string}`
) {
  try {
    sdk.setRightsBoundAccount(parentRbaAddress);
    console.log(`Parent owner claiming from parent RBA for period 0...`);
    // Parent RBA period will be 0 after claiming from child
    const claimTx = await sdk.rightsBoundAccount.claim(0n);
    await sdk.publicClient.waitForTransactionReceipt({ hash: claimTx });
    console.log("Parent owner claimed from parent RBA.");
  } catch (e) {
    console.error("Error claiming from parent RBA:", e);
    if (e.message.includes("NothingToClaim")) {
      console.warn("Parent share already claimed or not available.");
    } else {
      throw e;
    }
  }
}

// --- Step 5: Verifier Rewards Distribution ---
async function distributeVerifierRewards(
  sdk: CulturaSDK,
  parentAssetId: bigint,
  parentRbaAddress: `0x${string}`
) {
  try {
    console.log(`Distributing verifier rewards for parent ${parentAssetId}...`);
    const distributeTx = await sdk.verifierModule.distributeRewards(
      assetContractAddress,
      parentAssetId,
      parentRbaAddress,
      0n // Parent RBA period after claiming from child
    );
    await sdk.publicClient.waitForTransactionReceipt({ hash: distributeTx });
    console.log("Verifier rewards distributed to VerifierModule.");
  } catch (e) {
    console.error("Error distributing verifier rewards:", e);
    throw e;
  }
}

// --- Step 6: Individual Verifier Claims ---
async function claimVerifierReward(sdk: CulturaSDK, parentAssetId: bigint) {
  try {
    const verifierAddress = sdk.walletClient!.account!.address;
    const claimable = await sdk.verifierModule.calculateClaimableRewards(
      assetContractAddress,
      parentAssetId,
      verifierAddress
    );
    console.log(
      `Verifier claimable amount for parent ${parentAssetId}: ${claimable}`
    );

    if (claimable > 0n) {
      console.log("Verifier claiming rewards...");
      const claimTx = await sdk.verifierModule.claimRewards(
        assetContractAddress,
        parentAssetId
      );
      await sdk.publicClient.waitForTransactionReceipt({ hash: claimTx });
      console.log("Verifier rewards claimed.");
    } else {
      console.log("No rewards available to claim.");
    }
  } catch (e) {
    console.error("Error claiming verifier reward:", e);
    throw e;
  }
}
```

### Off-Chain Payment Flow

```typescript theme={null}
// --- Off-Chain Payment Flow ---
async function offChainPaymentFlow(
  licenseeSdk: CulturaSDK,
  parentOwnerSdk: CulturaSDK,
  sponsorSdk: CulturaSDK,
  sponsorAddress: `0x${string}`,
  licenseeAddress: `0x${string}`,
  parentOwnerAddress: `0x${string}`
) {
  try {
    // Step 1: Register off-chain royalty period (delegated)
    console.log("Registering off-chain royalty period...");
    const offchainRoyaltyDue = parseEther("5");
    const startDate = BigInt(Math.floor(Date.now() / 1000));
    const endDate = startDate + BigInt(365 * 24 * 60 * 60);
    const offchainPeriodIndex =
      await licenseeSdk.royalty.getRoyaltyInfoCount(licensedAssetId);

    const registrationSignature =
      await licenseeSdk.royalty.signForDelegatedRegisterRoyaltyDue(
        licensedAssetId,
        offchainRoyaltyDue,
        startDate,
        endDate,
        offchainPeriodIndex
      );

    await sponsorSdk.royalty.delegatedRegisterRoyaltyDue(
      licensedAssetId,
      offchainRoyaltyDue,
      startDate,
      endDate,
      offchainPeriodIndex,
      registrationSignature
    );
    console.log("Off-chain royalty period registered.");

    // Step 2: Report off-chain payment (delegated)
    console.log("Reporting off-chain payment...");
    const paymentAmount = parseEther("1");
    const paymentMetadata = JSON.stringify({
      paymentMethod: "Bank Transfer",
      referenceNumber: "BT-2023-12345",
      paymentDate: new Date().toISOString(),
      notes: "Payment for Q3 licensing",
    });

    const reportSignature =
      await licenseeSdk.royalty.signForDelegatedOffchainReportRoyalty(
        licensedAssetId,
        offchainPeriodIndex,
        paymentAmount
      );

    await sponsorSdk.royalty.delegatedReportOffChainPayment(
      licensedAssetId,
      offchainPeriodIndex,
      paymentAmount,
      paymentMetadata,
      reportSignature
    );
    console.log("Off-chain payment reported.");

    // Step 3: Parent acceptance (sponsored fees)
    console.log("Parent accepting off-chain payment...");

    // Get parent's allocated amount
    const parentAcceptanceInfo =
      await licenseeSdk.royalty.getParentAcceptanceInfo(
        licensedAssetId,
        offchainPeriodIndex,
        parent1RbaAddress
      );
    const allocatedAmount = parentAcceptanceInfo.allocatedAmount;

    if (allocatedAmount > 0n) {
      // Parent owner signs acceptance
      const acceptSignature =
        await parentOwnerSdk.royalty.signForAcceptOffChainPaymentByParent(
          parent1RbaAddress,
          allocatedAmount,
          offchainPeriodIndex,
          sponsorAddress
        );

      // Sponsor executes acceptance (paying fees)
      await sponsorSdk.royalty.acceptOffChainPaymentByParent(
        licensedAssetId,
        offchainPeriodIndex,
        parent1RbaAddress,
        acceptSignature,
        parentOwnerAddress,
        sponsorAddress
      );
      console.log("Off-chain payment accepted by parent.");
    }

    // Step 4: Check acceptance status
    const allParentInfo = await licenseeSdk.royalty.getAllParentAcceptanceInfo(
      licensedAssetId,
      offchainPeriodIndex,
      assetContractAddress
    );

    console.log("Parent acceptance statuses:");
    for (const parentInfo of allParentInfo) {
      console.log(`- Parent RBA: ${parentInfo.parentBoundAccount}`);
      console.log(`  Status: ${parentInfo.acceptanceInfo.status}`);
      console.log(`  Amount: ${parentInfo.acceptanceInfo.allocatedAmount}`);
    }
  } catch (e) {
    console.error("Error in off-chain payment flow:", e);
    throw e;
  }
}

// --- Alternative: Parent Denial Flow ---
async function denyOffChainPayment(
  parentOwnerSdk: CulturaSDK,
  sponsorSdk: CulturaSDK,
  parentOwnerAddress: `0x${string}`,
  parentRbaAddress: `0x${string}`,
  periodIndex: bigint
) {
  try {
    console.log("Parent denying off-chain payment...");

    const denySignature =
      await parentOwnerSdk.royalty.signForDenyOffChainPaymentByParent(
        licensedAssetId,
        periodIndex,
        parentRbaAddress
      );

    await sponsorSdk.royalty.denyOffChainPaymentByParent(
      licensedAssetId,
      periodIndex,
      parentRbaAddress,
      denySignature,
      parentOwnerAddress
    );
    console.log("Off-chain payment denied by parent.");
  } catch (e) {
    console.error("Error denying off-chain payment:", e);
    throw e;
  }
}
```

## Key Features in v0.2.0

### 1. **Sponsorship Support**

* **Gas-Free User Experience**: Sponsors can cover all transaction costs
* **Flexible Business Models**: Enables subscription, freemium, and sponsored content models
* **Enterprise Workflows**: Complex multi-party operations with clear cost attribution

### 2. **Multi-Level RBA Architecture**

* **Hierarchical Claims**: Child RBAs (VR) distribute to Parent RBAs, then to owners
* **Proportional Distribution**: Automatic calculation based on royalty splits
* **Separate Verifier Pools**: Individual reward pools for each parent asset

### 3. **Off-Chain Payment Integration**

* **Traditional Payment Methods**: Support for bank transfers, credit cards, etc.
* **Democratic Acceptance**: Each parent can individually accept or deny payments
* **Audit Trail**: Complete metadata tracking for compliance and transparency
* **Dispute Resolution**: Built-in denial mechanism for contested payments

### 4. **Enhanced Developer Experience**

* **Direct Returns**: Attestation functions return UIDs and addresses directly
* **Comprehensive Error Handling**: Clear error messages and recovery patterns
* **Parallel Operations**: Support for batch processing and optimization

This updated flow provides a robust foundation for both traditional blockchain payments and modern off-chain payment integration, making the Cultura ecosystem accessible to a broader range of users and business models.
