ESP Royalty System
The ESP royalty system provides economic incentives for data publishers by allowing them to earn royalties when developers re-register the same data on-chain. This creates a self-sustaining economic model that rewards data creators for preventing duplicate uploads and ensures the protocol's long-term viability.
Overview
The royalty system works through a two-phase process:
- Registration Phase: Publishers register data and pay gas costs, earning the right to collect royalties
- Re-registration Phase: Developers pay royalties when they want to re-register the same data on-chain, with publishers receiving the majority of the payment
How Royalties Work
Royalty Calculation
Royalties are calculated based on gas usage during the initial data storage:
royalty_amount = gas_used × royalty_rate
Where:
gas_used: Gas consumed duringwriteDataPointoperationroyalty_rate: Configurable rate set by the protocol owner
Royalty Distribution
When royalties are paid:
- Publisher: Receives
royalty_amount - protocol_fee - Protocol: Receives
royalty_amount / 10(10% fee) - Publisher: Receives
royalty_amount * 9 / 10(90% of royalties)
Core Functions
getDataPointRoyalty(bytes32 _dataPointAddress)
Gets the royalty cost for re-registering a data point.
// Get royalty cost
const address = await dataPointStorage.calculateAddress(data);
const royaltyCost = await dataPointRegistry.getDataPointRoyalty(address);
console.log("Royalty cost:", ethers.formatEther(royaltyCost), "ETH");
Returns:
uint256- Royalty cost in wei
collectRoyalties(uint256 _amount, address _withdrawTo)
Allows publishers to withdraw their earned royalties.
// Withdraw royalties
const amount = ethers.parseEther("0.1"); // 0.1 ETH
const tx = await dataPointRegistry.collectRoyalties(amount, signer.address);
await tx.wait();
Parameters:
_amount: Amount to withdraw in wei_withdrawTo: Address to send royalties to
Events:
RoyaltiesCollected(address indexed publisher, uint256 amount, address indexed withdrawTo)
royaltyBalance(address _publisher)
Checks the royalty balance of a publisher.
// Check royalty balance
const balance = await dataPointRegistry.royaltyBalance(publisherAddress);
console.log("Royalty balance:", ethers.formatEther(balance), "ETH");
Returns:
uint256- Current balance in wei
Royalty Lifecycle
1. Data Registration
// Publisher registers data and pays gas
const data = ethers.toUtf8Bytes("My valuable content");
const publisherAddress = signer.address;
// This will track gas usage and set up royalty collection
const tx = await dataPointRegistry.registerDataPoint(data, publisherAddress);
await tx.wait();
// Gas usage is automatically tracked and stored
console.log("Data registered with royalty tracking");
2. Data Re-registration
// Developer wants to re-register the same data and pays royalties
const address = await dataPointStorage.calculateAddress(data);
const royaltyCost = await dataPointRegistry.getDataPointRoyalty(address);
if (royaltyCost > 0) {
// Pay royalties to re-register the same data
const tx = await dataPointRegistry.registerDataPoint(data, ethers.ZeroAddress, {
value: royaltyCost
});
await tx.wait();
// Publisher earns royalties (minus protocol fee)
console.log("Royalties paid:", ethers.formatEther(royaltyCost));
}
3. Royalty Collection
// Publisher withdraws earned royalties
const balance = await dataPointRegistry.royaltyBalance(publisherAddress);
if (balance > 0) {
const tx = await dataPointRegistry.collectRoyalties(balance, publisherAddress);
await tx.wait();
console.log("Royalties collected:", ethers.formatEther(balance));
}
Royalty-Free Data
Waiving Royalties
Publishers can waive royalties by using address(0) as the publisher:
// Register data without royalties
const data = ethers.toUtf8Bytes("Free content");
const tx = await dataPointRegistry.registerDataPoint(data, ethers.ZeroAddress);
await tx.wait();
// This data can be re-registered without paying royalties
console.log("Data registered without royalties");
Use Cases for Royalty-Free Data
- Public domain content
- Open source documentation
- Community resources
- Educational materials
- Protocol metadata
Advanced Royalty Management
Batch Royalty Collection
// Collect royalties for multiple publishers
async function collectAllRoyalties(publishers: string[]) {
for (const publisher of publishers) {
const balance = await dataPointRegistry.royaltyBalance(publisher);
if (balance > 0) {
const tx = await dataPointRegistry.collectRoyalties(balance, publisher);
await tx.wait();
console.log(`Collected ${ethers.formatEther(balance)} for ${publisher}`);
}
}
}
Royalty Analytics
// Track royalty earnings over time
class RoyaltyTracker {
private registry: DataPointRegistry;
constructor(registry: DataPointRegistry) {
this.registry = registry;
}
async getPublisherStats(publisher: string) {
const balance = await this.registry.royaltyBalance(publisher);
return {
currentBalance: ethers.formatEther(balance),
balanceWei: balance.toString()
};
}
async getTotalRoyaltiesPaid(dataPointAddress: string) {
// This would require additional tracking - simplified example
const royaltyCost = await this.registry.getDataPointRoyalty(dataPointAddress);
return ethers.formatEther(royaltyCost);
}
}
Conditional Royalty Payment
// Pay royalties only if data exists and has royalties
async function accessDataWithRoyalties(data: Uint8Array) {
const address = await dataPointStorage.calculateAddress(data);
const royaltyCost = await dataPointRegistry.getDataPointRoyalty(address);
if (royaltyCost > 0) {
// Data exists and has royalties
const tx = await dataPointRegistry.registerDataPoint(data, ethers.ZeroAddress, {
value: royaltyCost
});
await tx.wait();
console.log("Royalties paid:", ethers.formatEther(royaltyCost));
} else {
// Data doesn't exist or has no royalties
const tx = await dataPointRegistry.registerDataPoint(data, ethers.ZeroAddress);
await tx.wait();
console.log("No royalties required");
}
}
Economic Model
Incentive Structure
The royalty system creates several incentives:
- Data Quality: Publishers are incentivized to create valuable content
- Data Availability: Publishers maintain access to their data
- Protocol Sustainability: Protocol fees support ongoing development
- Fair Compensation: Publishers receive 90% of royalty payments
Economic Parameters
// Protocol parameters
const PROTOCOL_FEE_RATE = 0.1; // 10%
const PUBLISHER_SHARE = 0.9; // 90%
// Royalty calculation
function calculateRoyaltyDistribution(royaltyAmount: bigint) {
const protocolFee = royaltyAmount / 10n;
const publisherShare = royaltyAmount - protocolFee;
return {
protocolFee,
publisherShare,
total: royaltyAmount
};
}
Gas-Based Royalty Calculation
// Royalty is based on gas usage during storage
async function estimateRoyaltyCost(data: Uint8Array, royaltyRate: bigint) {
// Estimate gas for writeDataPoint
const gasEstimate = await dataPointStorage.writeDataPoint.estimateGas(data);
// Calculate royalty cost
const royaltyCost = gasEstimate * royaltyRate;
return {
gasEstimate: gasEstimate.toString(),
royaltyCost: ethers.formatEther(royaltyCost),
royaltyCostWei: royaltyCost.toString()
};
}
Integration Examples
With DApps
// DApp integration for content monetization
class ContentDApp {
private registry: DataPointRegistry;
private storage: DataPointStorage;
constructor(registry: DataPointRegistry, storage: DataPointStorage) {
this.registry = registry;
this.storage = storage;
}
async publishContent(content: string, publisher: string) {
const data = ethers.toUtf8Bytes(content);
const tx = await this.registry.registerDataPoint(data, publisher);
await tx.wait();
const address = await this.storage.calculateAddress(data);
return {
address,
txHash: tx.hash,
publisher
};
}
async reRegisterContent(data: Uint8Array, developerAddress: string) {
const address = await this.storage.calculateAddress(data);
const royaltyCost = await this.registry.getDataPointRoyalty(address);
if (royaltyCost > 0) {
// Developer pays royalties to re-register
const tx = await this.registry.registerDataPoint(
data,
ethers.ZeroAddress,
{ value: royaltyCost }
);
await tx.wait();
}
return address;
}
}
With Smart Contracts
// Solidity contract for royalty management
contract RoyaltyManager {
IDataPointRegistry public registry;
constructor(address _registry) {
registry = IDataPointRegistry(_registry);
}
function publishWithRoyalties(bytes memory data, address publisher) external {
registry.registerDataPoint(data, publisher);
}
function reRegisterWithRoyalties(bytes memory data) external payable {
registry.registerDataPoint{value: msg.value}(data, address(0));
}
function collectRoyalties(uint256 amount) external {
registry.collectRoyalties(amount, msg.sender);
}
}
With Frontend Applications
// React component for royalty management
import { useState, useEffect } from 'react';
function RoyaltyDashboard({ registry, publisherAddress }) {
const [balance, setBalance] = useState('0');
const [loading, setLoading] = useState(false);
useEffect(() => {
loadBalance();
}, [publisherAddress]);
const loadBalance = async () => {
const balance = await registry.royaltyBalance(publisherAddress);
setBalance(ethers.formatEther(balance));
};
const collectRoyalties = async () => {
setLoading(true);
try {
const balance = await registry.royaltyBalance(publisherAddress);
if (balance > 0) {
const tx = await registry.collectRoyalties(balance, publisherAddress);
await tx.wait();
await loadBalance();
}
} catch (error) {
console.error("Failed to collect royalties:", error);
} finally {
setLoading(false);
}
};
return (
<div>
<h3>Royalty Balance: {balance} ETH</h3>
<button
onClick={collectRoyalties}
disabled={loading || balance === '0.0'}
>
{loading ? 'Collecting...' : 'Collect Royalties'}
</button>
</div>
);
}
Monitoring and Analytics
Event Monitoring
// Monitor royalty events
const royaltyPaidFilter = dataPointRegistry.filters.RoyaltiesPaid();
dataPointRegistry.on(royaltyPaidFilter, (dataPointAddress, payer, amount) => {
console.log("Royalty paid:", {
dataPoint: dataPointAddress,
payer: payer,
amount: ethers.formatEther(amount)
});
});
const royaltyCollectedFilter = dataPointRegistry.filters.RoyaltiesCollected();
dataPointRegistry.on(royaltyCollectedFilter, (publisher, amount, withdrawTo) => {
console.log("Royalty collected:", {
publisher: publisher,
amount: ethers.formatEther(amount),
withdrawTo: withdrawTo
});
});
Royalty Analytics
// Track royalty performance
class RoyaltyAnalytics {
private registry: DataPointRegistry;
constructor(registry: DataPointRegistry) {
this.registry = registry;
}
async getPublisherPerformance(publisher: string) {
const balance = await this.registry.royaltyBalance(publisher);
return {
currentBalance: ethers.formatEther(balance),
balanceWei: balance.toString()
};
}
async getDataPointRoyaltyInfo(dataPointAddress: string) {
const royaltyCost = await this.registry.getDataPointRoyalty(dataPointAddress);
return {
royaltyCost: ethers.formatEther(royaltyCost),
royaltyCostWei: royaltyCost.toString()
};
}
}
Best Practices
Gas Optimization
// Optimize gas usage for royalty calculations
async function registerDataOptimally(data: Uint8Array, publisher: string) {
// Use appropriate gas limits
const gasEstimate = await dataPointRegistry.registerDataPoint.estimateGas(data, publisher);
const tx = await dataPointRegistry.registerDataPoint(data, publisher, {
gasLimit: gasEstimate.mul(120).div(100) // 20% buffer
});
await tx.wait();
}
Error Handling
// Comprehensive error handling for royalty operations
async function handleRoyaltyPayment(data: Uint8Array) {
try {
const address = await dataPointStorage.calculateAddress(data);
const royaltyCost = await dataPointRegistry.getDataPointRoyalty(address);
if (royaltyCost > 0) {
const balance = await provider.getBalance(signer.address);
if (balance < royaltyCost) {
throw new Error("Insufficient balance for royalty payment");
}
const tx = await dataPointRegistry.registerDataPoint(data, ethers.ZeroAddress, {
value: royaltyCost
});
await tx.wait();
}
} catch (error) {
console.error("Royalty payment failed:", error);
throw error;
}
}
Security Considerations
// Validate royalty payments
function validateRoyaltyPayment(amount: bigint, expectedCost: bigint): boolean {
if (amount < expectedCost) {
throw new Error("Insufficient royalty payment");
}
if (amount > expectedCost * 2n) {
console.warn("Royalty payment significantly higher than expected");
}
return true;
}
Troubleshooting
Common Issues
Insufficient Royalty Payment
// Check royalty cost before payment
const royaltyCost = await dataPointRegistry.getDataPointRoyalty(address);
const balance = await provider.getBalance(signer.address);
if (balance < royaltyCost) {
throw new Error(`Insufficient balance. Need ${ethers.formatEther(royaltyCost)} ETH`);
}
Royalty Collection Failures
// Check balance before collection
const balance = await dataPointRegistry.royaltyBalance(publisherAddress);
if (balance === 0n) {
console.log("No royalties to collect");
return;
}
// Collect with proper error handling
try {
const tx = await dataPointRegistry.collectRoyalties(balance, publisherAddress);
await tx.wait();
} catch (error) {
console.error("Royalty collection failed:", error);
}