Decentralized Advertising Platform
Where advertisers, publishers, and viewers all earn fair value
Features • Architecture • Quick Start • API • Contracts • AI Agents
Web3Ads is a full-stack decentralized advertising platform that fairly distributes revenue between advertisers, publishers, and viewers. Built on Base L2 for low-cost transactions, it features privacy-preserving ad tracking using Semaphore zkProofs and supports gasless transactions so viewers never need ETH to withdraw earnings.
Revenue Distribution:
- Publishers: 50% of ad spend
- Viewers: 20% of ad spend
- Platform: 30% of ad spend
- Create and fund campaigns with native ETH
- Target ads by category (DeFi, NFT, Gaming, etc.)
- Real-time analytics: impressions, clicks, spend
- Multiple ad formats: banner, square, sidebar, interstitial
- Embed ads via React SDK (
web3ads-react) - Automatic revenue tracking per impression
- Gasless withdrawals to any wallet
- Simple integration: single React component
- Earn ETH by viewing ads (no wallet needed initially)
- Privacy-preserving tracking via zkProofs
- Gasless withdrawals - platform pays gas fees
- Chrome extension for identity management
- Base L2: Low gas costs (~$0.01 per transaction)
- Semaphore zkProofs: Anonymous ad tracking without compromising privacy
- ERC-2771 Gasless: Viewers withdraw without owning ETH
- x402 Protocol: AI agents can pay for APIs using ad earnings
- MCP Server: Model Context Protocol for Claude/AI agent integration
┌─────────────────────────────────────────────────────────────────────────┐
│ WEB3ADS PLATFORM │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Advertisers │ │ Publishers │ │ Viewers │ │
│ │ (Web App) │ │ (React SDK) │ │ (Extension) │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ EXPRESS API SERVER │ │
│ │ • Campaign CRUD • Ad Serving • Impression Tracking │ │
│ │ • Reward Calc • zkProof Verify • x402 Payments │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Smart Contracts │ │ Semaphore │ │ MCP Server │ │
│ │ (Base L2) │ │ (zkProofs) │ │ (AI Agents) │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Project Structure:
web3ads/
├── client/ # React web app (Vite + TypeScript + Tailwind)
├── server/ # Express API (Prisma + PostgreSQL)
├── contracts/ # Solidity smart contracts (Foundry)
├── extension/ # Chrome extension (React + Manifest V3)
└── packages/
├── react/ # web3ads-react npm package
├── mcp-server/ # MCP server for AI agents
└── openclaw-skill/ # OpenClaw skill-pack
- Node.js >= 22
- pnpm (
npm install -g pnpm) - PostgreSQL (via Supabase or local)
# Clone the repository
git clone https://github.com/kunalshah017/web3ads.git
cd web3ads
# Install all dependencies
pnpm install
# Set up environment variables
cp server/.env.example server/.env
# Edit server/.env with your database URL and keys# Run all services
pnpm dev
# Or run individually:
pnpm dev:client # React app → http://localhost:5173
pnpm dev:server # API server → http://localhost:3001
pnpm dev:extension # Extension with HMRpnpm build # Build all
pnpm build:client # Build React app
pnpm build:server # Build API server
pnpm build:extension # Build Chrome extensionBase URL: http://localhost:3001/api
| Endpoint | Method | Description |
|---|---|---|
/campaigns |
GET | List all campaigns |
/campaigns |
POST | Create new campaign |
/campaigns/:id |
GET | Get campaign details |
/campaigns/:id/stats |
GET | Get campaign analytics |
| Endpoint | Method | Description |
|---|---|---|
/ads/serve |
GET | Fetch ad for display |
/ads/impression |
POST | Record ad impression |
/ads/click |
POST | Record ad click |
| Endpoint | Method | Description |
|---|---|---|
/publishers/register |
POST | Register as publisher |
/publishers/stats |
GET | Get publisher earnings |
/publishers/embed-code |
GET | Get embed snippet |
| Endpoint | Method | Description |
|---|---|---|
/viewers/register |
POST | Register viewer identity |
/viewers/profile |
GET | Get viewer profile |
/viewers/stats |
GET | Get viewer earnings |
| Endpoint | Method | Description |
|---|---|---|
/rewards/balance |
GET | Check earnings balance |
/rewards/withdraw |
POST | Withdraw earnings (gasless) |
/rewards/history |
GET | Get withdrawal history |
| Endpoint | Method | Description |
|---|---|---|
/x402-info |
GET | Get x402 payment info |
| Contract | Address | Description |
|---|---|---|
| Web3AdsCoreV2 | 0xff7DB767900a8151a1D55b3cC4C72Eb0DA482d1F |
Main platform contract (ETH) |
| Forwarder | 0x8Bc2D17889EF9d04AA620e7984D7E7f74305215E |
ERC-2771 gasless forwarder |
// Advertiser creates campaign with ETH
function createCampaign(string name, uint8 adType, string mediaUrl) payable
// Record verified impression (backend only)
function recordImpression(campaignId, publisher, viewer, nullifier, signature)
// Viewer withdraws earnings (gasless via backend)
function withdrawViewer(viewer, amount, signature)
// Publisher withdraws earnings
function withdrawPublisher(amount)
function withdrawPublisherTo(recipient, amount, signature) // gaslessPUBLISHER_SHARE = 50% // Publisher gets 50% of ad spend
VIEWER_SHARE = 20% // Viewer gets 20% of ad spend
PLATFORM_SHARE = 30% // Platform gets 30% of ad spendcd contracts
# Install Foundry dependencies
forge install
# Run tests
forge test -vvv
# Deploy locally
forge script script/DeployV2.s.sol --broadcastnpm install web3ads-react
# or
pnpm add web3ads-reactimport { Web3Ad } from "web3ads-react";
function App() {
return (
<Web3Ad
publisherWallet="0x..."
type="banner" // 'banner' | 'square' | 'sidebar' | 'interstitial'
category="defi" // optional targeting
onImpression={(id) => console.log("Impression:", id)}
onClick={(id) => console.log("Clicked:", id)}
/>
);
}| Type | Dimensions | Use Case |
|---|---|---|
banner |
728×90 | Header/footer |
square |
300×250 | Sidebar content |
sidebar |
160×600 | Side navigation |
interstitial |
640×480 | Full-screen overlay |
- Viewability tracking: Uses IntersectionObserver (50% visible for 1s)
- Extension detection: Automatically detects Web3Ads extension
- zkProof coordination: Works with extension for privacy-preserving tracking
- Automatic fallback: Shows placeholder when no ads available
-
Build the extension:
pnpm dev:extension
-
Load in Chrome:
- Navigate to
chrome://extensions - Enable "Developer mode"
- Click "Load unpacked"
- Select
extension/distdirectory
- Navigate to
- Identity Storage: Stores Semaphore identity for zkProof generation
- Wallet Linking: Associates identity with Ethereum wallet
- Earnings Display: Shows accumulated earnings from viewing ads
- Switch Wallet: Clear identity and link new wallet
The extension uses CSP-safe detection via data-web3ads-extension attribute instead of inline script injection, ensuring compatibility with strict CSP policies.
The platform includes an MCP (Model Context Protocol) server that enables AI agents like Claude to interact with Web3Ads programmatically.
# Via npx
npx web3ads-mcp
# Or install globally
npm install -g web3ads-mcpAdd to your Claude Desktop config (claude_desktop_config.json):
{
"mcpServers": {
"web3ads": {
"command": "npx",
"args": ["web3ads-mcp"],
"env": {
"WEB3ADS_API_URL": "https://api.web3ads.wtf"
}
}
}
}| Tool | Description |
|---|---|
web3ads_check_balance |
Check user's ad earnings balance |
web3ads_make_payment |
Pay using ad earnings (gasless) |
web3ads_get_earnings |
Detailed earnings breakdown |
web3ads_create_campaign |
Create ad campaign via x402 |
web3ads_platform_info |
Platform info and pricing |
web3ads_list_campaigns |
List user's campaigns |
web3ads_x402_protocol |
x402 payment integration info |
The platform implements the x402 protocol (HTTP 402 Payment Required), allowing AI agents to pay for API calls using Web3Ads earnings:
AI Agent → x402 API Request → 402 Payment Required
→ Pay with Web3Ads Balance → Access Granted
This enables a unique feature: earnings from viewing ads can pay for any x402-compatible API.
For HeyElsa's OpenClaw framework, see packages/openclaw-skill/.
Web3Ads enables AI agents to execute real on-chain transactions through the MCP server. This creates a bridge between AI decision-making and blockchain execution.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ AI Agent │────▶│ MCP Server │────▶│ Backend API │────▶│ Smart Contract│
│ (Claude) │ │ (web3ads-mcp)│ │ (Express) │ │ (Base Sepolia)│
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
│ │ │ │
│ "withdraw my │ HTTP POST to │ Sign & submit │
│ earnings" │ /api/rewards/ │ transaction │
│ │ withdraw │ │
▼ ▼ ▼ ▼
User prompt Tool call Backend signer On-chain tx
pays gas fees executed
User to AI: "Withdraw my Web3Ads earnings to 0xABC..."
AI Agent executes:
// 1. MCP tool checks balance
web3ads_check_balance({ walletAddress: "0xUser..." });
// → Returns: { pending: 0.05, claimed: 0.02, total: 0.07 }
// 2. MCP tool initiates withdrawal
web3ads_make_payment({
walletAddress: "0xUser...",
amount: 0.05,
recipient: "0xABC...",
});Backend executes on-chain:
// server/src/blockchain/index.ts
const tx = await walletClient.writeContract({
address: WEB3ADS_CORE_V2,
abi: Web3AdsCoreV2ABI,
functionName: "withdrawViewer",
args: [viewerAddress, amount, signature],
});
// Backend wallet pays gas, funds go to recipientResult: Real ETH transferred on Base Sepolia, tx hash returned to AI agent.
| MCP Tool | Contract Function | On-Chain Effect |
|---|---|---|
web3ads_make_payment |
withdrawViewer() |
ETH sent to recipient |
web3ads_create_campaign |
createCampaign() |
Campaign created, ETH deposited |
web3ads_fund_campaign |
(via API) | Additional budget added |
All AI-initiated transactions are gasless for the user:
- Backend wallet holds ETH for gas fees
- ERC-2771 Forwarder enables meta-transactions
- User never needs to sign or pay gas
Viewer Withdrawal: 0x... (Base Sepolia)
Campaign Creation: 0x... (Base Sepolia)
Web3Ads implements a transparent revenue distribution model where funds flow through smart contracts with fixed percentage splits.
ADVERTISER SMART CONTRACT RECIPIENTS
────────── ────────────── ──────────
│ │ │
│ createCampaign() │ │
│ + 1.0 ETH deposit │ │
│ ──────────────────────────▶│ │
│ │ │
│ │ [Campaign Budget Pool] │
│ │ Budget: 1.0 ETH │
│ │ Spent: 0.0 ETH │
│ │ │
│ │ │
═════╪════════════════════════════╪════════════════════════════╪═════
│ AD IMPRESSION EVENT │ │
═════╪════════════════════════════╪════════════════════════════╪═════
│ │ │
│ │ recordImpression() │
│ │ Cost: 0.001 ETH │
│ │ │
│ │────▶ Publisher: 0.0005 ETH │
│ │ (50% share) │
│ │ │
│ │────▶ Viewer: 0.0002 ETH │
│ │ (20% share) │
│ │ │
│ │────▶ Platform: 0.0003 ETH │
│ │ (30% share) │
│ │ │
═════╪════════════════════════════╪════════════════════════════╪═════
│ WITHDRAWALS │ │
═════╪════════════════════════════╪════════════════════════════╪═════
│ │ │
│ │◀──── Publisher calls │
│ │ withdrawPublisher() │
│ │ │
│ │◀──── Viewer calls via API │
│ │ withdrawViewer() │
│ │ (gasless!) │
// Web3AdsCoreV2.sol - Revenue split constants
PUBLISHER_SHARE = 50; // 50% to site owner displaying ad
VIEWER_SHARE = 20; // 20% to user who viewed ad
PLATFORM_SHARE = 30; // 30% to Web3Ads platform
SHARE_BASE = 100;
// Per-impression distribution
function recordImpression(...) {
uint256 cost = getCPMRate(adType) / 1000; // Cost per impression
// Calculate shares
uint256 publisherAmount = (cost * PUBLISHER_SHARE) / SHARE_BASE;
uint256 viewerAmount = (cost * VIEWER_SHARE) / SHARE_BASE;
uint256 platformAmount = cost - publisherAmount - viewerAmount;
// Update balances (on-chain state)
publisherBalances[publisher] += publisherAmount;
viewerBalances[viewer] += viewerAmount;
platformBalance += platformAmount;
// Deduct from campaign budget
campaigns[campaignId].spent += cost;
}| Ad Type | CPM Rate (Demo) | Per Impression | Publisher Gets | Viewer Gets |
|---|---|---|---|---|
| Banner | 0.5 ETH | 0.0005 ETH | 0.00025 ETH | 0.0001 ETH |
| Square | 0.75 ETH | 0.00075 ETH | 0.000375 ETH | 0.00015 ETH |
| Sidebar | 1.0 ETH | 0.001 ETH | 0.0005 ETH | 0.0002 ETH |
| Interstitial | 2.0 ETH | 0.002 ETH | 0.001 ETH | 0.0004 ETH |
Demo rates are inflated 500x for hackathon demonstration purposes.
Publisher Withdrawal:
// Direct withdrawal (publisher pays gas)
function withdrawPublisher(uint256 amount) external {
require(publisherBalances[msg.sender] >= amount);
publisherBalances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
// Gasless withdrawal (backend pays gas)
function withdrawPublisherTo(address recipient, uint256 amount, bytes signature) external {
// Verify backend signature
// Transfer to arbitrary recipient
}Viewer Withdrawal (Always Gasless):
function withdrawViewer(address viewer, uint256 amount, bytes signature) external {
// Only callable by backend with valid signature
// Viewer never pays gas
require(viewerBalances[viewer] >= amount);
viewerBalances[viewer] -= amount;
payable(viewer).transfer(amount);
}- ReentrancyGuard: Prevents reentrancy attacks on withdrawals
- Backend Signatures: All sensitive operations require EIP-712 signatures
- Nullifier Tracking: Each impression can only be recorded once (zkProof)
- Budget Limits: Campaigns cannot spend more than deposited
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string |
BACKEND_SIGNER_PRIVATE_KEY |
Yes | Private key for signing |
BASE_SEPOLIA_RPC_URL |
No | RPC endpoint (default: public) |
PLATFORM_WALLET_ADDRESS |
No | Platform fee recipient |
| Variable | Required | Description |
|---|---|---|
VITE_API_URL |
No | API server URL |
VITE_WALLETCONNECT_PROJECT_ID |
Yes | WalletConnect project ID |
- React 19 + TypeScript
- Vite 8
- Tailwind CSS 4
- wagmi + RainbowKit (wallet connection)
- Express 5 + TypeScript
- Prisma 7 (ORM)
- Supabase PostgreSQL
- viem (blockchain interactions)
- Solidity 0.8.24
- Foundry (testing/deployment)
- OpenZeppelin (security)
- React 19 + TypeScript
- Vite 6 + Turborepo
- Chrome Manifest V3
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing) - Commit changes (
git commit -m 'feat: add amazing feature') - Push to branch (
git push origin feature/amazing) - Open a Pull Request
MIT License - see LICENSE for details.
Built with ❤️ for the decentralized web




