Skip to content

nithishjude/Lepton

Repository files navigation

🎵 Provenance Pay

Credit where credit is due. On-chain. At playback speed.

"Every second a track plays, every hand that made it gets paid."

Arc Testnet Circle Nanopayments Next.js Solidity License: MIT


Overview

Provenance Pay is a real-time music royalty micropayment system that bridges open music credit metadata directly into gas-free on-chain USDC payments — triggered second-by-second as a track plays.

The music industry has a 100-year-old attribution problem. Composers, producers, session musicians, and engineers are credited in open databases like MusicBrainz, but royalty collection is opaque, slow, and riddled with middlemen. Provenance Pay solves this by:

  1. Reading the full contributor graph from MusicBrainz (the world's richest open music credits DB)
  2. Registering split percentages immutably on-chain via the ProvenanceRegistry contract on Arc Testnet
  3. Firing Circle Nanopayments to every contributor wallet simultaneously, in real time, as the track plays

Table of Contents


Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                        BROWSER (Next.js 15)                         │
│                                                                     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌────────┐  │
│  │  Dashboard   │  │  Provenance  │  │   Library /  │  │ Claim  │  │
│  │  Live Ticks  │  │  Graph (D3)  │  │   Upload     │  │ Portal │  │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘  └───┬────┘  │
│         │                 │                  │              │        │
└─────────┼─────────────────┼──────────────────┼──────────────┼────────┘
          │                 │  HTTP / SSE       │              │
          │                 │                   │              │
┌─────────▼─────────────────▼───────────────────▼──────────────▼────────┐
│                    NEXT.JS API ROUTES  (port 3000)                     │
│                                                                        │
│   /api/track/[id]/provenance    /api/escrow/claim    /api/config       │
│   /api/library/tracks           /api/wallets          /api/metrics     │
└─────────────────────────────────┬──────────────────────────────────────┘
                                  │  IPC / HTTP (localhost)
                                  │
┌─────────────────────────────────▼──────────────────────────────────────┐
│               PROVENANCE AGENT SIDECAR  (Node.js, port 3001)           │
│                                                                        │
│  ┌──────────────────┐   ┌──────────────────┐   ┌─────────────────────┐│
│  │  mb-graph-       │   │  payment-         │   │  wallet-            ││
│  │  builder.js      │   │  dispatcher.js    │   │  provisioner.js     ││
│  │                  │   │                   │   │                     ││
│  │ • Fetch MBID     │   │ • EIP-3009 auth   │   │ • Circle SDK        ││
│  │   from MB API    │   │ • Circle Gateway  │   │ • Create EOA        ││
│  │ • Build split    │   │ • Auto-fund gas   │   │ • Escrow flag       ││
│  │   graph          │   │ • Poll tx hash    │   │ • SQLite upsert     ││
│  │ • Normalize      │   │ • SSE fan-out     │   │                     ││
│  │   contributor    │   │                   │   │                     ││
│  │   roles          │   │                   │   │                     ││
│  └────────┬─────────┘   └─────────┬─────────┘   └─────────┬───────────┘│
│           │                       │                        │            │
│  ┌────────▼───────────────────────▼────────────────────────▼───────────┐│
│  │                     SQLite  (data/provenance.db)                    ││
│  │  contributors · provenance_graphs · payment_ticks · corrections     ││
│  └─────────────────────────────────────────────────────────────────────┘│
│                                  │                                      │
│             ┌────────────────────┼──────────────────────┐               │
│             │     index.js       │                       │               │
│             │ POST /webhook      │   GET /api/payments/  │               │
│             │ • Play-gate timer  │   stream  (SSE)       │               │
│             │ • Stop/Skip        │ • tick events         │               │
│             │ • Graph ready evt  │ • gate_cleared        │               │
│             └────────────────────┘──────────────────────┘               │
└─────────────────────────────────┬──────────────────────────────────────┘
                                  │
        ┌─────────────────────────┴────────────────────────────┐
        │                                                       │
        ▼                                                       ▼
┌───────────────────┐                              ┌───────────────────────┐
│   MUSICBRAINZ     │                              │   CIRCLE DEVELOPER    │
│   REST API        │                              │   CONTROLLED WALLETS  │
│                   │                              │   (MPC, ARC-TESTNET)  │
│ • /ws/2/recording │                              │                       │
│   /{mbid}?inc=    │                              │ • createTransaction() │
│   artists+rels    │                              │ • createWallets()     │
│ • Open music      │                              │ • getWalletBalance()  │
│   credit graph    │                              │                       │
└───────────────────┘                              └─────────┬─────────────┘
                                                             │
                                                   ┌─────────▼─────────────┐
                                                   │   ARC TESTNET         │
                                                   │   Chain ID: 5042002   │
                                                   │                       │
                                                   │  ProvenanceRegistry   │
                                                   │  0x43c878be9d3d55e8a  │
                                                   │  5fa8e6ddd05c97df751  │
                                                   │  3004                 │
                                                   │                       │
                                                   │  Malachite BFT        │
                                                   │  Sub-second finality  │
                                                   │  USDC as gas token    │
                                                   └───────────────────────┘

System Components

1. Next.js Frontend (port 3000)

The interactive web application with 8 main sections:

Route Description
/ Landing page with hero, features, FAQ
/dashboard Live payment dashboard with animated USDC counters and area charts
/graph D3.js force-directed provenance graph, animates during active playback
/library Track library — browse and trigger playback for any seeded/uploaded track
/upload Upload a track and associate it with a MusicBrainz MBID
/wallets View Circle Agent Wallets (escrowed) and Developer-Controlled Wallets
/transactions Full payment history with Arc Testnet tx hashes
/settings Configure sidecar runtime parameters (play-gate, rate, batch interval)
/contributor/[walletAddress] Per-contributor earnings breakdown
/claim Claim portal for unregistered contributors to withdraw escrowed USDC

Key frontend tech:

  • Next.js 15 with App Router, React 19
  • D3.js v7 — force-directed graph simulation with real-time node pulsing
  • Recharts — live area chart for earnings over time
  • Framer Motion — micro-animations on ticks and state transitions
  • Tailwind CSS v4 + custom CSS animations
  • Sonner — toast notifications for payment events
  • SSE (EventSource) — subscribes to GET /api/payments/stream on the sidecar

2. Provenance Agent Sidecar (port 3001)

A standalone Node.js + Express server that acts as the autonomous payment agent. It:

  • Receives playback webhooks (POST /webhook)
  • Builds and caches provenance graphs from MusicBrainz
  • Reads/writes the ProvenanceRegistry contract on Arc Testnet via viem
  • Dispatches USDC payments via Circle Developer-Controlled Wallets API
  • Fans out real-time events via Server-Sent Events

Core modules:

File Responsibility
sidecar/index.js Express server, webhook handler, play-gate timer, SSE broadcaster, contract interaction
sidecar/mb-graph-builder.js MusicBrainz API client, contributor graph construction, split normalization
sidecar/payment-dispatcher.js Circle SDK payment dispatch, token ID resolution, auto-gas-funding, DB logging
sidecar/wallet-provisioner.js Circle EOA wallet creation for unregistered contributors, escrow tagging
sidecar/db/index.js SQLite schema initialization and seed data

3. ProvenanceRegistry.sol (Arc Testnet)

An EVM smart contract providing write-once, immutable royalty split storage keyed by MusicBrainz Recording ID.

Deployed at: 0x43c878be9d3d55e8a5fa8e6ddd05c97df7513004
Network: Arc Testnet (Chain ID: 5042002)
Deployment tx: 0xa8c1f7bc36a9f155fc2c0086fa118bebfd9fa46eeffdb825f975ba2da4830492


Data Flow

Happy Path: Track Plays → Everyone Gets Paid

Media Player / Navidrome
        │
        │  POST /webhook { event:"play", mbid:"<mbid>" }
        ▼
   Sidecar index.js
        │
        ├── 1. buildProvenanceGraph(mbid)
        │         │
        │         ├── Check SQLite cache
        │         │   └── [CACHE HIT] → return cached graph
        │         │
        │         └── [CACHE MISS] → fetch MusicBrainz API
        │               GET /ws/2/recording/{mbid}?inc=artists+artist-rels+work-rels
        │               │
        │               ├── Extract artist-credit[] → role: "artist"
        │               ├── Extract relations[] → normalize roles:
        │               │     mix/*     → "mixer"
        │               │     produce/* → "producer"
        │               │     compose/* → "composer"
        │               │     vocal/*   → "featured_artist"
        │               │     instrument/* → "session_musician"
        │               │
        │               ├── computeSplits() → basis points (sum = 10000)
        │               │     artist: 35%, producer: 25%, featured: 15%
        │               │     composer: 10%, mixer: 8%, session: 7%
        │               │     (normalized proportionally per credited roles)
        │               │
        │               └── Cache graph in SQLite
        │
        ├── 2. emit('graph_ready') → SSE → Dashboard graph animates
        │
        ├── 3. Play-Gate Timer (15 seconds)
        │         │
        │         └── [SKIP/STOP received] → clearTimeout → NO PAYMENT
        │
        └── 4. [Gate Cleared] → setInterval every 15s
                  │
                  └── dispatcher.dispatchTick(graph, 0.0001 USDC/sec)
                        │
                        ├── For each split:
                        │     ├── resolveWallet(contributorMbid)
                        │     │     ├── [Known] → SQLite lookup → address
                        │     │     └── [Unknown] → Circle SDK createWallets()
                        │     │                      → tag as ESCROW → SQLite
                        │     │
                        │     └── client.createTransaction({
                        │               walletId: PLATFORM_WALLET_ID,
                        │               tokenId: USDC_TOKEN_ID,
                        │               destinationAddress: contributor.address,
                        │               amounts: [(totalTick * bps/10000).toFixed(6)]
                        │           })
                        │           → Poll for txHash
                        │           → INSERT into payment_ticks
                        │
                        └── emit('tick', confirmations) → SSE → UI updates

Claim Flow: Unregistered Contributor Claims Escrow

Contributor visits /claim → enters wallet address
        │
        ▼
GET /api/escrow/balance?address={addr}
        │
        └── SQLite: SELECT SUM(amount_usdc) WHERE contributor wallet matches
                → Returns claimable balance
        │
        ▼
POST /api/escrow/claim { address, signature }
        │
        └── Verify signature
        └── Circle SDK: transfer from escrow wallet → contributor address
        └── Update SQLite: mark as claimed

Smart Contract

ProvenanceRegistry.sol

// Write-once per MBID. Sum of bps must equal 10000 (100.00%).
function register(
    bytes32          mbid,     // bytes32-encoded UTF-8 MusicBrainz Recording ID
    address[] calldata wallets, // contributor wallet addresses
    uint16[]  calldata bps,     // basis points (e.g. 5000 = 50%)
    string[]  calldata roles    // human-readable role labels
) external;

// Read splits for payment dispatch
function getSplits(bytes32 mbid) external view returns (Split[] memory);

// Check if already registered (write-once guard)
function isRegistered(bytes32 mbid) external view returns (bool);

// Propose a correction (doesn't overwrite — creates on-chain audit trail)
function proposeCorrection(
    bytes32 mbid,
    address[] calldata newWallets,
    uint16[] calldata newBps,
    string[] calldata newRoles,
    string calldata reason
) external;

Key design decisions:

  • Write-once: Once an MBID is registered, its split cannot be mutated — preventing royalty manipulation
  • Correction proposal: Disputes create an on-chain audit trail via CorrectionProposed events without changing the canonical split
  • bytes32 MBID encoding: UUIDs are stored as left-padded bytes32 for gas efficiency
  • Basis points: All percentages stored as uint16 out of 10000 for precision without floats

Sidecar Agent API

All endpoints served by the Node.js sidecar at http://localhost:3001.

Playback Control

Method Endpoint Body Description
POST /webhook { event, mbid } Trigger play / stop / skip
POST /api/simulate-play { mbid } Manually simulate playback (demo use)

Data Endpoints

Method Endpoint Description
GET /api/payments/stream?trackId={mbid} SSE stream of payment ticks
GET /api/track/{mbid}/provenance Full provenance graph JSON
GET /api/metrics Aggregate payment stats
GET /api/transactions Full payment history
GET /api/contributors All known contributors
GET /api/wallets All Circle wallets (with escrow flag)
GET /api/library Track library (seeded + uploaded)

Configuration

Method Endpoint Description
POST /api/config Update runtime config (play-gate, rate, batch interval)
GET /api/config Read current sidecar configuration

SSE Event Types

{ "type": "graph_ready",  "graph": { ... } }
{ "type": "gate_cleared", "trackId": "..." }
{ "type": "tick",         "confirmations": [ { "contributorMbid", "amount", "txRef", "status" } ] }
{ "type": "stopped",      "trackId": "..." }

Database Schema

SQLite database at app/data/provenance.db (auto-created on first run).

-- All known music contributors
CREATE TABLE contributors (
    id              TEXT PRIMARY KEY,       -- UUID
    mbid            TEXT UNIQUE NOT NULL,   -- MusicBrainz Artist ID
    name            TEXT NOT NULL,
    wallet_address  TEXT,                   -- Circle wallet address on Arc Testnet
    wallet_id       TEXT,                   -- Circle internal wallet ID
    is_provisioned  INTEGER DEFAULT 0,      -- 1 if Circle wallet created
    is_escrow       INTEGER DEFAULT 0,      -- 1 if unregistered (funds held)
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- Cached provenance graphs (MusicBrainz + computed splits)
CREATE TABLE provenance_graphs (
    mbid        TEXT PRIMARY KEY,           -- MusicBrainz Recording ID
    graph_json  TEXT NOT NULL,              -- Full graph JSON blob
    arc_tx_hash TEXT,                       -- Arc Testnet registration tx
    created_at  DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- Every individual payment dispatched
CREATE TABLE payment_ticks (
    id               TEXT PRIMARY KEY,
    track_mbid       TEXT NOT NULL,
    contributor_mbid TEXT NOT NULL,
    amount_usdc      TEXT NOT NULL,         -- Decimal string (6 places)
    nanopay_ref      TEXT,                  -- Circle transaction ID
    arc_batch_hash   TEXT,                  -- Arc Testnet tx hash
    tick_at          DATETIME DEFAULT CURRENT_TIMESTAMP
);

-- On-chain correction proposals
CREATE TABLE corrections (
    id             TEXT PRIMARY KEY,
    track_mbid     TEXT NOT NULL,
    correction_json TEXT NOT NULL,
    arc_tx_hash    TEXT,
    reason         TEXT NOT NULL,
    created_at     DATETIME DEFAULT CURRENT_TIMESTAMP
);

Split Calculation

The sidecar computes royalty splits using a weighted role system normalized to exactly 10,000 basis points (100.00%):

Role Default Weight Notes
artist 35% Primary credited artist
producer 25% Track producer
featured_artist 15% Featuring vocalist
composer 10% Songwriter / composer
mixer 8% Mix engineer
session_musician 7% Instrumentalist

Normalization logic:

  1. Each contributor's weight is looked up by role
  2. All weights are summed
  3. Each contributor's final bps = floor(weight / total * 10000)
  4. Rounding remainders are allocated to the highest-weighted contributor
  5. Resulting splits always sum to exactly 10000

Once computed, splits are registered to the ProvenanceRegistry contract — permanently immutable for that MBID.


Circle Stack Integration

Provenance Pay uses all 7 layers of the Circle Agent Stack:

Layer How it's used
Arc Testnet EVM settlement layer for USDC transfers and ProvenanceRegistry contract. Chain ID 5042002. Malachite BFT sub-second finality.
Circle Nanopayments (Gateway) createTransaction() via Developer Controlled Wallets API — processes gas-free USDC transfers that settle on Arc Testnet in batches
x402 Protocol x402 payment gating on stream endpoints (x402StreamGating config flag)
Agent Wallets Auto-provisioned EOA wallets on ARC-TESTNET for unregistered contributors. Funds escrow safely.
Circle CLI Used during setup to register entity secret, configure wallet sets, and set spending policies
Developer-Controlled Wallets (MPC) Platform wallet (sender) and registered contributor wallets managed via @circle-fin/developer-controlled-wallets SDK
Circle Contracts ProvenanceRegistry.sol deployed on Arc Testnet for tamper-proof royalty split storage

Project Structure

lepton/
│
├── README.md                        ← You are here
├── provenance-pay-prd.md            ← Full Product Requirements Document
├── .env                             ← Root env (shared with sidecar)
│
├── contracts/                       ← Foundry smart contract project
│   ├── src/
│   │   └── ProvenanceRegistry.sol   ← Main registry contract
│   ├── script/
│   │   └── Deploy.s.sol             ← Foundry deployment script
│   ├── test/                        ← Contract unit tests
│   ├── broadcast/                   ← Deployment receipts (Arc Testnet)
│   └── foundry.toml                 ← Foundry config (Arc Testnet RPC)
│
├── app/                             ← Next.js app + Node.js sidecar
│   ├── .env                         ← App-level env (same values as root)
│   ├── package.json                 ← Dependencies + npm scripts
│   │
│   ├── sidecar/                     ← Node.js provenance agent
│   │   ├── index.js                 ← Express server, webhook, SSE, contract writes
│   │   ├── mb-graph-builder.js      ← MusicBrainz API + split computation
│   │   ├── payment-dispatcher.js    ← Circle SDK payment dispatch
│   │   ├── wallet-provisioner.js    ← Circle wallet creation for unknown contributors
│   │   └── db/
│   │       └── index.js             ← SQLite schema + seed data
│   │
│   └── app/                         ← Next.js App Router
│       ├── layout.tsx               ← Root layout, fonts, metadata
│       ├── page.tsx                 ← Landing page
│       ├── globals.css              ← Global styles + CSS animations
│       ├── providers.tsx            ← React context providers
│       │
│       ├── components/
│       │   ├── AppShell.tsx         ← Sidebar navigation shell
│       │   ├── ProvenanceGraph.tsx  ← D3 force-directed graph component
│       │   ├── LiveCounters.tsx     ← Animated USDC ticker component
│       │   ├── landing/             ← Landing page sections
│       │   │   ├── Hero.tsx
│       │   │   ├── Features.tsx
│       │   │   ├── Ecosystem.tsx
│       │   │   ├── FAQ.tsx
│       │   │   ├── Navbar.tsx
│       │   │   └── Footer.tsx
│       │   └── ui/                  ← Reusable UI primitives
│       │
│       ├── context/
│       │   └── PlaybackContext.tsx  ← Global playback state
│       │
│       ├── claim/
│       │   └── page.tsx             ← Public claim portal (no auth required)
│       │
│       └── (app)/                   ← Authenticated app shell routes
│           ├── layout.tsx           ← App layout with sidebar
│           ├── dashboard/page.tsx   ← Live payment dashboard
│           ├── graph/page.tsx       ← D3 provenance graph visualizer
│           ├── library/page.tsx     ← Track library
│           ├── upload/page.tsx      ← Track upload + MBID association
│           ├── wallets/page.tsx     ← Wallet management
│           ├── transactions/page.tsx ← Payment history
│           ├── settings/page.tsx    ← Sidecar runtime config
│           └── contributor/
│               └── [walletAddress]/page.tsx ← Contributor detail

Environment Variables

Both .env files (root and app/) should have the same values:

# ── Circle API ────────────────────────────────────────────────────────────────
CIRCLE_API_KEY=TEST_API_KEY:...           # Circle Developer Portal API key
CIRCLE_ENTITY_SECRET=...                  # 32-byte hex entity secret (registered via CLI)
CIRCLE_WALLET_SET_ID=...                  # Circle wallet set UUID

# ── Platform Wallet ───────────────────────────────────────────────────────────
PLATFORM_WALLET_ADDRESS=0x...             # On-chain address (Arc Testnet)
PLATFORM_WALLET_ID=...                    # Circle internal wallet UUID (sender)

# ── Arc Testnet ───────────────────────────────────────────────────────────────
ARC_CHAIN_ID=5042002
ARC_RPC_URL=https://rpc.testnet.arc.network

# ── Smart Contract ────────────────────────────────────────────────────────────
PROVENANCE_REGISTRY_ADDRESS=0x43c878be9d3d55e8a5fa8e6ddd05c97df7513004
DEPLOYER_PRIVATE_KEY=0x...               # Private key for contract writes

# ── Sidecar Agent ─────────────────────────────────────────────────────────────
SIDECAR_PORT=3001
NAVIDROME_URL=http://localhost:4533       # Optional: media server
NAVIDROME_WEBHOOK_SECRET=...             # Optional: webhook HMAC secret

# ── MusicBrainz (rate-limit compliance) ───────────────────────────────────────
MUSICBRAINZ_APP_NAME=ProvenancePay
MUSICBRAINZ_APP_VERSION=1.0
MUSICBRAINZ_CONTACT=your@email.com

Running Locally

Prerequisites

  • Node.js 20+
  • npm 10+

1. Install dependencies

# Root-level scripts
npm install

# Next.js app + sidecar
npm --prefix app install

2. Start the full stack

# From the repo root — starts Next.js (3000) + Sidecar (3001) concurrently
cd app
npm run dev:all

Or run each service separately:

# Terminal 1 — Next.js frontend
cd app && npm run dev

# Terminal 2 — Node.js sidecar agent
cd app && npm run sidecar

3. Open the app

Service URL
Next.js Frontend http://localhost:3000
Provenance Agent Sidecar http://localhost:3001

4. Trigger a demo payment

The DB is pre-seeded with 3 tracks. Navigate to Library and click Play on any track. The dashboard will show live USDC ticks after the 15-second play gate clears.

Or trigger via curl:

curl -X POST http://localhost:3001/webhook \
  -H "Content-Type: application/json" \
  -d '{"event":"play","mbid":"f980fc14-e29b-481d-ad3a-5ed9b4ab6340"}'

Deployment

Smart Contract (Foundry)

cd contracts

# Build
forge build

# Deploy to Arc Testnet
forge script script/Deploy.s.sol:Deploy \
  --rpc-url https://rpc.testnet.arc.network \
  --broadcast \
  --legacy

# Verify on Arc block explorer
forge verify-contract \
  <DEPLOYED_ADDRESS> \
  src/ProvenanceRegistry.sol:ProvenanceRegistry \
  --rpc-url https://rpc.testnet.arc.network

Already deployed: 0x43c878be9d3d55e8a5fa8e6ddd05c97df7513004 on Arc Testnet (Chain ID: 5042002)

Frontend (Vercel)

cd app
npx vercel --prod

Set all environment variables in Vercel dashboard (same as .env).

Sidecar Agent (Railway / Render)

The sidecar is a standalone Express server. Deploy app/sidecar/ with:

node --env-file=.env sidecar/index.js

Set SIDECAR_PORT and all Circle + Arc env vars in your hosting provider.


Arc Testnet Details

Property Value
Network Name Arc Testnet
Chain ID 5042002
RPC URL https://rpc.testnet.arc.network
Native Gas Token USDC
Consensus Malachite BFT
Block Explorer https://explorer.testnet.arc.network
ProvenanceRegistry 0x43c878be9d3d55e8a5fa8e6ddd05c97df7513004
Deployment Tx 0xa8c1f7bc36a9f155fc2c0086fa118bebfd9fa46eeffdb825f975ba2da4830492

License

MIT © 2026 Nithish Jude


Built with ❤️ at Lepton Hackathon · Powered by Circle · Settled on Arc Testnet

About

Real-time USDC royalty splits to every credited musician, settled on Arc Testnet. MusicBrainz metadata → on-chain split registry → Circle nanopayments, every 2 seconds.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages