Privacy-first sports fan advertising network, live on Aleo testnet.
A fan can prove they qualify for a sponsor's campaign — without revealing their spending, attendance, or loyalty data — and earn real USDCx. No fan data ever touches the blockchain.
Sports clubs know their fans deeply: spend history, attendance, loyalty tier. Sponsors want precision targeting. Today's solution? The club hands their fan database to the sponsor — a GDPR liability and a privacy disaster.
FanX eliminates that entirely using ZK proofs.
Club publishes Merkle root → 1 tx per season. Nothing else on-chain.
↓
Sponsor locks USDCx budget → sets targeting thresholds (spend / attendance / loyalty)
↓
Fan generates ZK proof → "I qualify. I won't tell you why."
↓
Contract verifies → Merkle membership ✅ Thresholds met ✅ No double-claim ✅
↓
Fan receives RewardTicket → private record → redeemable for USDCx
Sponsor sees → matched_count += 1 (nothing else)
The fan's spend, attendance, loyalty, and tier are private Leo inputs. The Aleo VM never exposes them — not in calldata, not in finalize, not anywhere.
┌──────────────┐ ┌─────────────────────────┐ ┌──────────────┐
│ Club │────▶│ fanx_contract_v3.aleo │◀────│ Sponsor │
│ │ │ │ │ │
│ Merkle tree │ │ update_season_root() │ │ create_ │
│ (off-chain) │ │ claim_reward() [ZK] │ │ campaign() │
│ Serves fan │ │ redeem_ticket() │────▶│ sees only: │
│ paths (API) │ │ │ │ count++ │
└──────────────┘ └─────────────────────────┘ └──────────────┘
| # | Function | Actor | Description |
|---|---|---|---|
| 1 | update_season_root |
Club | Publish BHP256 Merkle root — 1 tx/season per 1M fans |
| 2 | create_campaign |
Sponsor | Lock USDCx + set targeting thresholds |
| 3 | claim_reward |
Fan | ZK Merkle proof + threshold check → private RewardTicket |
| 4 | redeem_ticket |
Fan | Convert RewardTicket to public USDCx balance |
| 5 | deactivate_campaign |
Sponsor | Pause a campaign |
| 6 | sponsor_withdraw |
Sponsor | Reclaim unspent budget |
| 7 | register_club |
Admin | Authorize a sports club |
- 1 club tx per season — not 1 per fan
- Depth-20 Merkle tree → 1,048,576 fans per club per season
- Fan gas cost ~0.007 credits per claim
Verified April 12, 2026 — all mappings queryable via Aleo Explorer
| Contract | Network | Status |
|---|---|---|
fanx_contract_v3.aleo |
Aleo testnet | ✅ LIVE |
test_usdcx_stablecoin.aleo |
Aleo testnet | ✅ Dependency (pre-deployed) |
Key addresses:
- Admin:
aleo1s3qmy92sh6q7hgwdeqnzslycag0tfzdgzwqyh7pejkv23daqgq9qns8n7h - Program (for USDCx escrow):
aleo1z46smphzpz0tcw8vmu9ea8hq304a43tpm0lxc4drr4fy2uq8yyzs5ah24n
| Field | Value |
|---|---|
| Campaign ID | 2677790036399590415212541804383891666584254468809915654462441701520987625138field |
| Sport | ⚽ Soccer (All Sports / ID 0) |
| Min spend score | 8,000 / 10,000 |
| Min attendance | 8,000 / 10,000 |
| Min loyalty | 9,000 / 10,000 |
| Min tier | Gold (2) |
| Reward per claim | 1,000,000 µUSDCx = 1 USDCx |
| Total budget | 10,000,000 µUSDCx = 10 USDCx |
| Max claims | 10 fans |
# Verify yourself:
curl "https://api.explorer.provable.com/v1/testnet/program/fanx_contract_v3.aleo/mapping/c_budget/2677790036399590415212541804383891666584254468809915654462441701520987625138field"
# → "8000000u128" (8 USDCx remaining after 2 claims)
curl "https://api.explorer.provable.com/v1/testnet/program/fanx_contract_v3.aleo/mapping/matched_count/2677790036399590415212541804383891666584254468809915654462441701520987625138field"
# → "2u64" (2 fans have claimed so far)
curl "https://api.explorer.provable.com/v1/testnet/program/test_usdcx_stablecoin.aleo/mapping/balances/aleo17ha5h9c87d0lupnfg9yv3r2gwsjgzhrtne67thys96l9v26zgurslmj6x0"
# → "1000000u128" (Judge 1 already redeemed — 1 USDCx in wallet)| Event | TX ID |
|---|---|
| v3 Deploy | at13zzduc3tmyvsz5g4ze64ehmqv82wp9rf52sucvkk3cvwtv2spcgqkmtrzt |
| update_season_root | at1xl5zuh7r6ejwrqw9mnhjgk38f3cgd0f75ehxddwepgqfk7zsdqxs9lv8hu |
| create_campaign | at13cl7gvulea92d20wq46nzlad5exufakue5lwwzxl45xgpvy57spshj5n9k |
| Judge 1 claim_reward ✅ | at1e69mwhs06ld3uray6ly2tsr4lq75e2p794jzwsvnrw2jeny57grqsqyj2x |
| Judge 1 redeem_ticket ✅ | at1mj4y9wjplnpcp6w3w2audlj367vqfu07f2rv99jr6he5wh9pwgzqfe38p0 |
Each judge has a pre-registered fan wallet in their own private Merkle tree. All 3 can claim simultaneously — the fan_claimed key is unique per address, preventing any collision.
| Address | aleo17ha5h9c87d0lupnfg9yv3r2gwsjgzhrtne67thys96l9v26zgurslmj6x0 |
| Private Key | APrivateKey1zkp7Wt7BU1qVgApXKaNG3sWrSgeZqSVfokVekeGoZaXnHQF |
| Club ID | 10000field |
| USDCx balance | 1,000,000 µUSDCx (already redeemed ✅) |
| Address | aleo15w2ml049524ly7pss9rlzpu8g6mufqwvt4yhce8xrvzs8dpazspshxeh8d |
| Private Key | APrivateKey1zkpDKjwNJtQwjedTXPezSty36S2nXdYLyLSkTgKkjWCz1i9 |
| Club ID | 20000field |
| USDCx balance | 0 — claim yours! |
| Address | aleo1qtt9na7t2g6vgrlxj7sxmw9szrhsva2yqc8e62r5dme60pyqjcgqnct5f5 |
| Private Key | APrivateKey1zkp6zaFFYV4m8Zoegee2oaJwgdoci2nb8X81NkGrXoTB8Tq |
| Club ID | 30000field |
| USDCx balance | 0 — claim yours! |
Fan profile (same for all): Platinum Tier · Spend 9,500 · Attendance 9,500 · Loyalty 9,500 ✅
- Install Shield Wallet (Chrome extension)
- Click Import Wallet → paste your Private Key from above
- Set any password
- Open the FanX frontend (running locally or deployed URL)
- Click Connect Wallet → select Shield
- Your address appears top-right
- Go to Campaigns tab
- Click "⚡ Live: Premier Soccer Fan Rewards" (the active campaign)
- Click "Claim with ZK Proof"
- The modal loads your fan profile automatically from
fanx_proof.json - Approve in Shield Wallet → wait ~10s
What just happened:
- Your spend/attendance/loyalty/tier stayed private in your browser
- A BHP256 Merkle proof ran in the ZK circuit
- The contract verified you're a registered fan AND meet the thresholds
c_budgetdropped by 1 USDCx on-chainmatched_countincremented
- Go to My Rewards tab
- Click "Redeem → USDCx" on your RewardTicket
- Approve in Shield Wallet → ~10s
- Your USDCx balance → 1.000000 USDCx ✅
Open the claim TX in Aleo Explorer. Notice:
- Inputs #1–7: all encrypted — spend, attendance, loyalty, tier, Merkle path are
private - Inputs #8–16: public campaign parameters (campaign ID, thresholds, season)
- The fan's actual metrics appear nowhere on-chain
| Claim | Proof |
|---|---|
| Fan data never on-chain | spend, attend, loyalty, tier are private in Leo — the Aleo VM physically prevents them appearing in finalize arguments |
| Merkle membership is cryptographic | Leaf + 20-level BHP256 path runs inside the ZK circuit — one wrong byte fails the proof at the network level |
| Budget is real USDCx | c_budget decrements on-chain with every valid claim — verify live |
| No double-claiming | fan_claimed[BHP256(signer + campaign_id)] set permanently on-chain — replay is cryptographically impossible |
| Stablecoin payout is real | redeem_ticket calls test_usdcx_stablecoin.aleo/transfer_public — Judge 1's 1 USDCx balance verifiable on-chain |
| Feature | Traditional Ad Tech | FanX ZK |
|---|---|---|
| Fan data shared | ✅ Full profiles to sponsor | ❌ Zero — never leaves device |
| Sponsor sees | Name, email, spend history | Only matched_count |
| Eligibility check | Off-chain (trust required) | On-chain ZK (trustless) |
| Fake claim prevention | Platform-enforced (centralised) | Cryptographically impossible |
| GDPR compliance | Requires data agreements | Inherent — no data collected |
| Metric | Scale | Description |
|---|---|---|
spend_score |
0–10,000 | Lifetime fan spend (10,000 = top spender) |
attendance |
0–10,000 | Events attended this season |
loyalty_score |
0–10,000 | Platform tenure + engagement |
tier |
0–3 | 0=Bronze · 1=Silver · 2=Gold · 3=Platinum |
| ID | Sport | ID | Sport |
|---|---|---|---|
| 0 | ⚽ Soccer | 9 | 🥊 MMA / UFC |
| 1 | 🏀 Basketball | 10 | 🏎 Formula 1 |
| 2 | 🏈 American Football | 11 | 🚴 Cycling |
| 3 | ⚾ Baseball | 12 | 🏃 Athletics |
| 4 | 🏏 Cricket | 13 | 🥊 Boxing |
| 5 | 🏉 Rugby | 14 | 🏐 Volleyball |
| 6 | 🎾 Tennis | 15 | 🎮 Esports |
| 7 | ⛳ Golf | 255 | 🏆 All Sports |
| 8 | 🏒 Ice Hockey |
fanx/
contract/
program.json fanx_contract_v3.aleo manifest
src/main.leo Leo smart contract (ZK circuit)
tests/
helpers.mjs BHP256 Merkle tree + test utilities
0_setup.mjs Environment check
1_register_club.mjs Register virtual clubs
2_publish_root.mjs Build Merkle tree + publish on-chain root
3_create_campaign.mjs Create sponsor campaigns
4_claim_reward.mjs Fan claim with ZK proof
5_redeem_ticket.mjs Redeem RewardTicket for USDCx
6_sponsor_withdraw.mjs Reclaim budget
7_lifecycle.mjs End-to-end validation
frontend/
public/fanx_proof.json Pre-registered fan Merkle proofs (4 wallets)
src/
lib/
constants.ts Sports registry, tier definitions, config
merkle.ts Browser-side BHP256 Merkle tree
fanx-client.ts On-chain mapping fetchers + input builders
store.ts Zustand global state
pages/
Dashboard.tsx Campaign browser (fetches live chain data)
CampaignDetail.tsx Campaign detail + ZK claim flow
MyRewards.tsx Fan's pending RewardTickets + redeem
SponsorDashboard.tsx Create/manage campaigns
# Frontend
cd fanx/frontend
npm install
npm run dev
# → http://localhost:3000
# Tests (requires .env with PRIVATE_KEY)
cd fanx/tests
cp .env.example .env
npm install
node 7_lifecycle.mjs # full end-to-end validation- ZK Proofs: Aleo / Leo (Marlin zk-SNARK, AleoBFT consensus)
- Hash Function: BHP256 (collision-resistant, native in Leo circuit)
- Stablecoin:
test_usdcx_stablecoin.aleo(pre-deployed on testnet) - Frontend: React 18, TypeScript, Vite, Framer Motion
- State: Zustand (localStorage-persisted tickets)
- Wallet: Shield Wallet via
@provablehq/aleo-wallet-adaptor-react