PRIV ProtocolPRIV Docs
Contracts

AdNetwork

Privacy-preserving advertising network with multi-oracle consensus for impression verification.

Overview

AdNetwork is a decentralized advertising platform that enables advertisers to run campaigns and publishers to earn PRIV tokens for displaying ads. It uses multi-oracle consensus (M-of-N) for verifying impressions and clicks.

PropertyValue
Protocol Fee2.5%
Min Deposit1 PRIV
Min Claim Threshold10 PRIV
Max Price/Impression1 PRIV
Max Price/Click10 PRIV
Max Oracles10
NetworkBase (Chain ID: 8453)

Deployed Addresses

Base Sepolia (Testnet)

Base Sepolia0x97f4...84e8

Key Features

  • Advertiser Deposits: Advertisers deposit PRIV to fund campaigns
  • Campaign Creation: Create campaigns with budget, CPM/CPC pricing, and duration
  • Multi-Oracle Consensus: M-of-N oracle confirmations for impression verification
  • Publisher Earnings: Publishers earn from verified impressions/clicks (minus 2.5% fee)
  • Oracle Heartbeat: Staleness checks prevent stale data from being used
  • Emergency Claims: Publishers can claim below threshold when contract is paused

How It Works

Advertiser deposits PRIV  -->  Creates campaign  -->  Publisher shows ads
         |                          |                        |
         v                          v                        v
   Balance updated             Budget allocated        Impressions tracked
                                                              |
                                                              v
                                                    Oracles confirm batch
                                                              |
                                                              v
                                                    Publisher claims earnings

Multi-Oracle Consensus Flow

For impression verification, multiple oracles must agree:

Oracle 1 confirms  -->  Oracle 2 confirms  -->  Consensus reached
       |                       |                       |
       v                       v                       v
  count = 1               count = 2              Publisher credited
                    (if minOracleConfirmations = 2)

Contract Interface

Constants

/// @notice Protocol fee percentage (250 = 2.5%)
uint256 public constant PROTOCOL_FEE_BPS = 250;

/// @notice Minimum deposit amount (1 PRIV)
uint256 public constant MIN_DEPOSIT = 1e18;

/// @notice Minimum claim threshold (10 PRIV)
uint256 public constant MIN_CLAIM_THRESHOLD = 10e18;

/// @notice Maximum price per impression (1 PRIV)
uint256 public constant MAX_PRICE_PER_IMPRESSION = 1e18;

/// @notice Maximum price per click (10 PRIV)
uint256 public constant MAX_PRICE_PER_CLICK = 10e18;

/// @notice Maximum number of oracles
uint256 public constant MAX_ORACLES = 10;

Structs

Campaign

struct Campaign {
    address advertiser;          // Campaign owner
    bool active;                 // Whether campaign is active
    uint256 budget;              // Total budget in PRIV
    uint256 spent;               // Amount spent so far
    uint256 pricePerImpression;  // CPM price (per impression)
    uint256 pricePerClick;       // CPC price (per click)
    bytes32 metadataHash;        // IPFS/Arweave hash of campaign metadata
    uint256 totalImpressions;    // Total recorded impressions
    uint256 totalClicks;         // Total recorded clicks
    uint48 createdAt;            // Creation timestamp
    uint48 expiresAt;            // Expiration timestamp
}

PublisherStats

struct PublisherStats {
    uint256 totalImpressions;    // Total impressions served
    uint256 totalClicks;         // Total clicks generated
    uint256 totalEarnings;       // Total lifetime earnings
    uint256 pendingEarnings;     // Earnings available to claim
    uint256 lastClaimAt;         // Last claim timestamp
}

Functions

Advertiser Functions

deposit

Deposit PRIV tokens into advertiser balance.

function deposit(uint256 amount) external nonReentrant whenNotPaused
ParameterTypeDescription
amountuint256Amount to deposit (min: 1 PRIV)

Requirements:

  • Amount must be at least MIN_DEPOSIT (1 PRIV)
  • User must have approved the contract

withdraw

Withdraw PRIV tokens from advertiser balance.

function withdraw(uint256 amount) external nonReentrant
ParameterTypeDescription
amountuint256Amount to withdraw

createCampaign

Create a new advertising campaign.

function createCampaign(
    uint256 budget,
    uint256 pricePerImpression,
    uint256 pricePerClick,
    bytes32 metadataHash,
    uint256 duration
) external whenNotPaused returns (uint256 campaignId)
ParameterTypeDescription
budgetuint256Total budget (from advertiser balance)
pricePerImpressionuint256CPM price (max: 1 PRIV)
pricePerClickuint256CPC price (max: 10 PRIV)
metadataHashbytes32Campaign metadata hash
durationuint256Campaign duration in seconds

Requirements:

  • Budget must be greater than 0 and not exceed advertiser balance
  • At least one pricing model must be set (pricePerImpression or pricePerClick greater than 0)
  • Prices must not exceed maximums
  • Duration must be greater than 0

pauseCampaign

Pause an active campaign.

function pauseCampaign(uint256 campaignId) external

resumeCampaign

Resume a paused campaign.

function resumeCampaign(uint256 campaignId) external whenNotPaused

topUpCampaign

Add more budget to an existing campaign.

function topUpCampaign(uint256 campaignId, uint256 amount) external whenNotPaused

withdrawCampaignBudget

Withdraw remaining budget from an expired or paused campaign.

function withdrawCampaignBudget(uint256 campaignId) external nonReentrant

Multi-Oracle Consensus

confirmImpressionBatch

Confirm an impression batch as part of M-of-N consensus.

function confirmImpressionBatch(
    uint256 campaignId,
    address publisher,
    uint256 impressions,
    uint256 clicks,
    bytes32 batchId
) external onlyOracle notStaleOracle whenNotPaused
ParameterTypeDescription
campaignIduint256Campaign ID
publisheraddressPublisher address to credit
impressionsuint256Number of impressions
clicksuint256Number of clicks
batchIdbytes32Unique batch identifier (prevents replay)

Requirements:

  • Caller must be an authorized oracle
  • Oracle must have fresh heartbeat
  • Batch must not already be processed
  • Campaign must be active and not expired
  • At least one of impressions or clicks must be greater than 0

Consensus: When minOracleConfirmations oracles confirm the same batch parameters, the publisher is automatically credited (minus 2.5% protocol fee).


Legacy Oracle Functions (Single Oracle Mode)

These functions only work when minOracleConfirmations == 1:

recordImpressions

function recordImpressions(
    uint256 campaignId,
    address publisher,
    uint256 count
) external onlyOracle notStaleOracle whenNotPaused

recordClicks

function recordClicks(
    uint256 campaignId,
    address publisher,
    uint256 count
) external onlyOracle notStaleOracle whenNotPaused

Publisher Functions

claimEarnings

Claim accumulated earnings.

function claimEarnings() external nonReentrant whenNotPaused

Requirements:

  • Pending earnings must be at least MIN_CLAIM_THRESHOLD (10 PRIV)
  • Contract must not be paused

emergencyClaim

Emergency claim for publishers below threshold (only when paused).

function emergencyClaim() external nonReentrant whenPaused

Note: This allows claims of any amount but only works when the contract is paused (emergency mode).


View Functions

getCampaign

Get full campaign details.

function getCampaign(uint256 campaignId) external view returns (Campaign memory)

getPublisherStats

Get publisher statistics.

function getPublisherStats(address publisher) external view returns (PublisherStats memory)

isCampaignRunning

Check if a campaign is active, not expired, and has budget.

function isCampaignRunning(uint256 campaignId) external view returns (bool)

getRemainingBudget

Get remaining budget for a campaign.

function getRemainingBudget(uint256 campaignId) external view returns (uint256)

getOracles

Get all authorized oracle addresses.

function getOracles() external view returns (address[] memory)

isOracleFresh

Check if an oracle's data is fresh (not stale).

function isOracleFresh(address oracleAddress) external view returns (bool)

computeBatchHash

Compute the hash for a batch of impressions.

function computeBatchHash(
    uint256 campaignId,
    address publisher,
    uint256 impressions,
    uint256 clicks,
    bytes32 batchId
) external pure returns (bytes32)

Events

/// @notice Emitted when an advertiser deposits
event Deposited(address indexed advertiser, uint256 amount);

/// @notice Emitted when an advertiser withdraws
event Withdrawn(address indexed advertiser, uint256 amount);

/// @notice Emitted when a campaign is created
event CampaignCreated(
    uint256 indexed campaignId,
    address indexed advertiser,
    uint256 budget,
    uint256 pricePerImpression,
    uint256 pricePerClick,
    bytes32 metadataHash,
    uint48 expiresAt
);

/// @notice Emitted when a campaign is paused
event CampaignPaused(uint256 indexed campaignId);

/// @notice Emitted when a campaign is resumed
event CampaignResumed(uint256 indexed campaignId);

/// @notice Emitted when a campaign is topped up
event CampaignToppedUp(uint256 indexed campaignId, uint256 amount);

/// @notice Emitted when impressions are recorded
event ImpressionsRecorded(
    uint256 indexed campaignId,
    address indexed publisher,
    uint256 count,
    uint256 earnings
);

/// @notice Emitted when clicks are recorded
event ClicksRecorded(
    uint256 indexed campaignId,
    address indexed publisher,
    uint256 count,
    uint256 earnings
);

/// @notice Emitted when an oracle confirms a batch
event BatchConfirmed(
    bytes32 indexed batchId,
    address indexed oracle,
    uint256 confirmationCount,
    uint256 requiredConfirmations
);

/// @notice Emitted when a batch reaches consensus
event BatchProcessed(
    bytes32 indexed batchId,
    uint256 indexed campaignId,
    address indexed publisher,
    uint256 impressions,
    uint256 clicks,
    uint256 earnings
);

/// @notice Emitted when a publisher claims earnings
event EarningsClaimed(address indexed publisher, uint256 amount);

Errors

error InvalidAddress();
error InvalidAmount();
error InvalidPrice();
error InvalidDuration();
error InsufficientBalance();
error CampaignNotFound();
error CampaignNotActive();
error CampaignExpired();
error NotCampaignOwner();
error BelowClaimThreshold();
error NothingToClaim();
error OnlyOracle();
error CampaignBudgetExceeded();
error OracleDataStale();
error BatchAlreadyProcessed();
error BatchAlreadyConfirmed();
error ConsensusRequired();

Usage Examples

Create a CPM Campaign

import { parseEther } from 'viem'

async function createCampaign() {
  const budget = parseEther('1000')       // 1000 PRIV
  const cpm = parseEther('0.5')           // 0.5 PRIV per impression
  const cpc = parseEther('2')             // 2 PRIV per click
  const duration = 30 * 24 * 60 * 60      // 30 days
  const metadataHash = '0x...'            // Campaign metadata IPFS hash

  // 1. Deposit funds
  await writeContract({
    address: PRIV_TOKEN_ADDRESS,
    abi: privTokenAbi,
    functionName: 'approve',
    args: [ADNETWORK_ADDRESS, budget],
  })

  await writeContract({
    address: ADNETWORK_ADDRESS,
    abi: adNetworkAbi,
    functionName: 'deposit',
    args: [budget],
  })

  // 2. Create campaign
  await writeContract({
    address: ADNETWORK_ADDRESS,
    abi: adNetworkAbi,
    functionName: 'createCampaign',
    args: [budget, cpm, cpc, metadataHash, duration],
  })
}

Check Publisher Earnings

function usePublisherStats(publisher: string) {
  return useReadContract({
    address: ADNETWORK_ADDRESS,
    abi: adNetworkAbi,
    functionName: 'getPublisherStats',
    args: [publisher],
  })
}

Claim Earnings

async function claimEarnings() {
  await writeContract({
    address: ADNETWORK_ADDRESS,
    abi: adNetworkAbi,
    functionName: 'claimEarnings',
  })
}

Oracle: Confirm Impression Batch

// Called by authorized oracles
async function confirmBatch(
  campaignId: bigint,
  publisher: string,
  impressions: bigint,
  clicks: bigint,
  batchId: `0x${string}`
) {
  await writeContract({
    address: ADNETWORK_ADDRESS,
    abi: adNetworkAbi,
    functionName: 'confirmImpressionBatch',
    args: [campaignId, publisher, impressions, clicks, batchId],
  })
}

Campaign Metadata Schema

Recommended schema for metadataHash:

{
  "name": "Q1 2025 Brand Campaign",
  "description": "Awareness campaign for DeFi users",
  "creatives": [
    {
      "type": "banner",
      "size": "300x250",
      "url": "ipfs://Qm.../banner-300x250.png"
    },
    {
      "type": "banner",
      "size": "728x90",
      "url": "ipfs://Qm.../banner-728x90.png"
    }
  ],
  "targeting": {
    "categories": ["defi", "nft", "trading"],
    "geoTargeting": ["US", "EU", "APAC"]
  },
  "landingPage": "https://example.com/campaign",
  "trackingPixel": "https://track.example.com/pixel"
}

Security Notes

  1. Multi-Oracle Consensus: A single compromised oracle cannot drain campaign budgets. Multiple oracles must agree on impression counts before publishers are credited.

  2. Oracle Heartbeat: Oracles must maintain fresh heartbeats. Stale oracles (default: 1 hour without activity) cannot confirm batches.

  3. Batch ID Replay Prevention: Each batch ID can only be processed once. Oracles must use unique batch IDs for each submission.

  4. Protocol Fee: The 2.5% fee is deducted from publisher earnings, not advertiser deposits. Publishers receive 97.5% of the gross earnings.

  5. Minimum Claim Threshold: Publishers must accumulate at least 10 PRIV before claiming. This reduces gas costs from frequent small claims. Emergency claims bypass this when the contract is paused.

  6. Price Caps: Maximum prices prevent misconfigured campaigns from draining budgets too quickly.


Source Code

View on GitHub