Skip to content

charlesms1246/StormX

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

StormX

Own the player. Win the game. Rule the pitch.

StormX is a fully on-chain football player tokenization platform built on the Initia axi-cms rollup. Real Ligue 1 players become tradeable ERC-20 tokens. Fans stake their tokens in head-to-head matchups, buy club-branded merchandise NFTs, and purchase match tickets — all settled transparently on-chain.


Initia Hackathon Submission

Project Overview

StormX turns every Ligue 1 player into a deployed ERC-20 smart contract. Fans stake player tokens in head-to-head games where real on-chain performance scores — derived from live player stats (goals, assists, tackles) stored in each contract — determine the winner. Beyond gameplay, fans spend club fan tokens on ERC-721 merchandise NFTs and match tickets, creating a two-token economy where player performance and club loyalty are independent on-chain positions, all on the gasless Initia axi-cms rollup.

Implementation Detail

  • The Custom Implementation: The core original logic is PlayerToken.calculatePerformance() — a pure on-chain scoring function that converts real-world Ligue 1 stats into a 0–10 position-weighted score. The game contract calls this for each of the 10 staked tokens and determines the winner entirely in Solidity with no oracle callback. A stat oracle script feeds live match data into each player's individual ERC-20 contract (351 total), creating a direct link between real-world match outcomes and on-chain token economics. A bonding curve in each PlayerToken scales purchase price with demand, and a season-end reward pool splits 80% to token holders and 20% to the actual player.

  • The Native Feature: The Interwoven Bridge (openBridge from useInterwovenKit) is wired to a Bridge button in the top nav. Before this, new users had no in-app path to acquire assets on axi-cms — they needed to arrive with liquidity already on the rollup. The bridge removes that cold-start problem entirely: one click opens the InterwovenKit modal, the user picks a source chain and asset, and funds arrive on axi-cms ready to use in the game or marketplace without ever leaving the app. Initia Usernames (useUsernameQuery) are also integrated, replacing raw hex addresses with .init names in the nav and throughout the game flow.

How to Run Locally

  1. Start your axi-cms node — ensure it is reachable at http://localhost:8545 before continuing.
  2. Install dependencies — run npm install inside both web/ and contracts/.
  3. Create web/.env.local — set NEXT_PUBLIC_AXICMS_RPC_URL, NEXT_PUBLIC_AXICMS_CHAIN_ID=2177282315500993, NEXT_PUBLIC_GAME_CONTRACT_ADDRESS, and DEPLOYER_PRIVATE_KEY.
  4. Start the frontend — run npm run dev inside web/, then open http://localhost:3000. Connect MetaMask to axi-cms (Chain ID 2177282315500993, symbol GAS), click Faucet for test tokens, and click Bridge to open the Interwoven Bridge modal.

The Problem

Football fans have no real stake in the game. Existing fan engagement tools — loyalty apps, digital collectibles, fantasy leagues — are either centralized, offer no real ownership, or don't reflect on-field performance in any meaningful economic way.

Specifically:

  • No true ownership. Digital fan products live inside walled-garden apps. Fans can't trade, transfer, or actually own what they buy.
  • Performance is decorative. A player's real-world stats — goals, assists, minutes played — have zero bearing on the value of fan products tied to them.
  • Payments are opaque. Ticket and merchandise transactions go through intermediaries with no verifiable audit trail.
  • Fan loyalty is siloed. Club tokens, player tokens, and marketplace economies don't talk to each other.

The Solution

StormX turns every Ligue 1 player into a deployed ERC-20 smart contract. Fans hold tokens that represent real players. Those tokens have utility:

Action Mechanism
Play Stake 5 player tokens in a head-to-head game. Winner takes all 10.
Shop Buy limited merchandise NFTs using club fan tokens (PAR, MON, MAR…).
Attend Purchase on-chain match tickets for Ligue 1 fixtures using club fan tokens.
Collect View your NFTs, tickets, and token portfolio in your on-chain profile.

Everything — game outcomes, token transfers, NFT minting, ticket purchases — is recorded on the Initia axi-cms rollup with zero gas fees.

The platform also integrates three native Initia ecosystem features:

Feature What it does
Initia Usernames (.init) Connected wallets display their .init username in the nav instead of a raw hex address.
Interwoven Bridge A one-click Bridge button opens the native InterwovenKit bridge modal, letting users move assets from Initia L1 and other rollups into axi-cms.
Auto-signing (Session UX) Users enable a session key once; subsequent game transactions (approve, createGame, joinGame) sign automatically without a MetaMask popup per action.

Backstory

StormX was designed around one core belief: fan engagement should have real economic stakes, not just cosmetic ones.

The platform is built on axi-cms — a custom appchain built on the Initia L1 using the Interwoven Rollup stack. axi-cms runs a full EVM execution environment with gasless transactions and deterministic finality, inheriting security from Initia's validator set while operating as an independent rollup. It was specifically chosen for its ability to handle the scale of 351 individual player token contracts without gas friction. Each Ligue 1 player has their own deployed ERC-20 contract, making the token economy granular by design.

Club fan tokens (PAR, MON, MAR, LYO, LIL, LEN, NIC, REN…) were introduced as a separate payment layer for the marketplace and ticketing, creating a deliberate two-token model: player tokens for gameplay staking, club tokens for commerce. This separation means a fan's loyalty to a club and their interest in a specific player are both reflected on-chain independently.


Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        User Browser                             │
│                                                                 │
│   Next.js 15 + React 19 + Wagmi + InterwovenKit                 │
│   ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌────────────────┐  │
│   │  /game   │ │/marketplace│ │ /tickets │ │    /claim      │  │
│   │ Create   │ │ MerchNFT  │ │MatchTicket│ │ Profile/NFTs  │  │
│   │ Join     │ │ Buy w/ Fan│ │ Buy w/ Fan│ │ Club Tokens   │  │
│   │ Battle   │ │   Tokens  │ │   Tokens  │ │ My Tickets    │  │
│   └────┬─────┘ └─────┬─────┘ └─────┬────┘ └───────────────┘  │
└────────┼─────────────┼─────────────┼───────────────────────────┘
         │             │             │
         ▼             ▼             ▼
┌─────────────────────────────────────────────────────────────────┐
│                  Next.js API Routes (/api/*)                    │
│                                                                 │
│  /game/create   /game/join   /game/status                       │
│  /merch/seed    /tickets/seed                                   │
│  /faucet        /club-faucet                                    │
│  /tokens        /player      /standings                         │
│  /blockchain/*  /health                                         │
└────────────────────────────────┬────────────────────────────────┘
                                 │  viem (RPC calls)
                                 ▼
┌─────────────────────────────────────────────────────────────────┐
│              Initia axi-cms EVM Rollup                          │
│              Chain ID: 2177282315500993                         │
│              RPC: http://localhost:8545  │  Gas: 0              │
│                                                                 │
│  ┌──────────────────────┐   ┌──────────────────────────────┐   │
│  │  GameContractMulti   │   │  PlayerToken (×351)          │   │
│  │  Token               │   │  ERC-20 per Ligue 1 player   │   │
│  │                      │   │  0 decimals                  │   │
│  │  • createGame()      │   │  • mint() (onlyOwner)        │   │
│  │  • joinGame()        │   │  • Metadata: goals, assists  │   │
│  │  • 5 tokens × 200   │   │    appearances, season stats │   │
│  │  • winner-take-all   │   └──────────────────────────────┘   │
│  └──────────────────────┘                                       │
│                                                                 │
│  ┌──────────────────────┐   ┌──────────────────────────────┐   │
│  │  MerchNFT            │   │  TicketContract              │   │
│  │  ERC-721             │   │                              │   │
│  │                      │   │  • listTickets()             │   │
│  │  • createMerch()     │   │  • buyTicket()               │   │
│  │  • buyMerch()        │   │  • ERC-20 approve → buy      │   │
│  │  • IPFS metadata     │   │  • TicketPurchased event     │   │
│  │  • ERC-20 payment    │   └──────────────────────────────┘   │
│  └──────────────────────┘                                       │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  FanToken (×19 clubs)   ERC-20, 18 decimals              │   │
│  │  PAR · MON · MAR · LYO · LIL · LEN · NIC · REN · …      │   │
│  │  Used as payment for MerchNFT and TicketContract         │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘

Token Economy

                    ┌─────────────────────┐
                    │   Player Tokens     │
                    │   (0 decimals)      │
                    │   1 per player      │
                    │   351 contracts     │
                    └──────────┬──────────┘
                               │ stake 5 × 200
                               ▼
                    ┌─────────────────────┐
                    │  GameContract       │
                    │  Head-to-head game  │
                    │  Winner takes all   │
                    └─────────────────────┘

                    ┌─────────────────────┐
                    │   Club Fan Tokens   │
                    │   (18 decimals)     │
                    │   19 clubs          │
                    └──────┬──────────────┘
                           │ approve → buy
                ┌──────────┴──────────┐
                ▼                     ▼
     ┌──────────────────┐  ┌──────────────────┐
     │   MerchNFT       │  │  TicketContract  │
     │  6 items         │  │  6 fixtures      │
     │  PAR/MON/MAR/LYO │  │  Per home club   │
     └──────────────────┘  └──────────────────┘

Each merch item and ticket fixture is priced in the relevant club's fan token:

Item / Fixture Payment Token Price
Match Day VIP Pass PAR 80
PSG Signed Jersey NFT PAR 150
Goal of the Season MON 300
Fantasy Starter Pack LYO 50
Hall of Fame Card MAR 200
Monaco Special Edition MON 100
PSG vs Marseille (ticket) PAR 80
Monaco vs Lyon (ticket) MON 60
Lille vs Lens (ticket) LIL 50
Nice vs Rennes (ticket) NIC 45
PSG vs Monaco (ticket) PAR 120
Marseille vs Lyon (ticket) MAR 55

Tech Stack

Frontend

Layer Technology
Framework Next.js 15, React 19
Language TypeScript 5
Styling Tailwind CSS 4
Animations Framer Motion
3D Scene Three.js
Wallet Wagmi 2, Viem 2, InterwovenKit v2.6
Initia Native Usernames (useUsernameQuery), Bridge (openBridge)
UI Components Radix UI, shadcn/ui
Charts Recharts
IPFS Pinata Web3

Smart Contracts

Layer Technology
Language Solidity ^0.8.20
Framework Hardhat
Libraries OpenZeppelin 5 (ERC20, ERC721, Ownable)
Type Safety TypeChain
Randomness Block entropy (SimpleRandom.sol)

Chain

Property Value
Network axi-cms (custom Interwoven Rollup on Initia L1)
Chain ID 2177282315500993
RPC http://localhost:8545
Native Token GAS
Gas Price 0 (gasless)
Execution EVM-compatible
Security Inherited from Initia validator set

Smart Contracts

GameContractMultiToken

The core game engine. A player selects 5 player token contracts and calls createGame(). An opponent selects their 5 tokens and calls joinGame(). Both parties must hold ≥ 200 tokens per contract and approve the game contract to spend them. On join, all 10 token positions are staked and a winner is determined. The winner receives all staked tokens.

createGame(address[5] tokenContracts)
joinGame(bytes32 gameCode, address[5] tokenContracts)
getGameDetails(bytes32 gameCode) → GameState
userToGameCode(address) → bytes32

PlayerToken

One ERC-20 contract per player. 0 decimals. Stores on-chain player metadata: position, nationality, team, season stats (goals, assists, appearances, shots, duels, tackles). Owner can update stats via oracle scripts that pull from API Football.

FanToken

Minimal ERC-20 with 18 decimals. One per Ligue 1 club. mint() is owner-only — the server faucet (/api/club-faucet) mints 1,000 tokens per request for testing.

MerchNFT

ERC-721 with IPFS metadata. Items created by the deployer via createMerch(). Users buy with buyMerch(id) after approving the correct club fan token. Each item tracks minted vs supply count.

TicketContract

Lists match tickets with listTickets(matchId, price, qty, paymentToken). Fans buy with buyTicket(matchId) after approving the home club's fan token. Emits TicketPurchased(matchId, buyer, price, timestamp) — used to build the "My Tickets" profile tab.


Native Initia Features

Initia Usernames (.init)

The connect button resolves the connected wallet's initiaAddress to its .init username using InterwovenKit's useUsernameQuery hook. If the user has registered a .init name on Initia testnet, it replaces the shortened hex address in the nav. Falls back to the shortened address when no username exists.

// web/app/components/ConnectButton.tsx
const { data: username } = useUsernameQuery()
// Displays "alice.init" instead of "0x1234...abcd"
<span>{username ?? shortenAddress(initiaAddress)}</span>

Interwoven Bridge

A Bridge button in the top nav opens the native InterwovenKit bridge modal, which handles routing between Initia L1 (initiation-2), other Interwoven Rollups, and external ecosystems. If the user isn't connected, clicking it triggers the connect flow first.

// web/app/components/BridgeButton.tsx
openBridge({ srcChainId: 'initiation-2', srcDenom: 'uinit' })

Local dev note: The bridge modal resolves chains from the Initia public registry. Your local axi-cms instance may not appear as a destination until it is registered. The UI and connect flow work regardless.

Auto-signing (Session UX)

InterwovenKit's autosign feature derives a dedicated session wallet from the connected wallet's signature. The user approves a set of permitted message types once — InterwovenKitProvider is configured with enableAutoSign to whitelist /minievm.evm.v1.MsgCall (the Cosmos-level message wrapping every EVM contract call on axi-cms). After that, all game transactions — token approvals, createGame, and joinGame — are signed and broadcast automatically via submitTxBlock without triggering a MetaMask popup for each action.

Permissions are time-limited, scoped to the exact message types listed, and revocable at any time from the wallet. The session wallet is derived per app origin, so it cannot be reused across other sites.

// web/app/components/Provider.tsx
<InterwovenKitProvider
  {...TESTNET}
  defaultChainId="axi-cms"
  customChain={axicmsChain}
  enableAutoSign={{
    'axi-cms': ['/minievm.evm.v1.MsgCall'],
  }}
>

// web/app/components/GameCreation.tsx — game tx without popup
const { transactionHash } = await submitTxBlock({ messages, fee })

UX impact: A game requires up to 6 sequential MetaMask prompts (5 approvals + createGame). With autosign enabled that collapses to one setup prompt at session start, after which the entire game flow runs in the background without interruption.

Provider Configuration

InterwovenKitProvider is configured with {...TESTNET} spread first, then overridden with the custom axi-cms chain. This ensures all Initia service endpoints (registry, router, username module, bridge) are available while keeping axi-cms as the default chain.

// web/app/components/Provider.tsx
<InterwovenKitProvider
  {...TESTNET}               // testnet registry, router, username module, bridge
  defaultChainId="axi-cms"  // override home chain to our rollup
  customChain={axicmsChain} // inject axi-cms chain definition
>

Running Locally

Follow these steps in order. Each step depends on the previous one completing successfully.

Step 1 — Prerequisites

Make sure you have the following before continuing:

Requirement Version / Notes
Node.js 18 or higher
MetaMask Browser extension installed
Initia axi-cms node Must be running and reachable at http://localhost:8545

If your axi-cms node isn't running yet, start it with Weave CLI before proceeding. The frontend will fail to load any on-chain data without it.


Step 2 — Clone and Install Dependencies

git clone https://github.com/charlesms1246/StormX
cd StormX

Install frontend and contract dependencies separately:

# Frontend
cd web && npm install

# Contracts
cd ../contracts && npm install

Step 3 — Create the Environment File

Create web/.env.local with the following values. Do not skip this — the app will not connect to the chain without it.

# axi-cms RPC and chain config
NEXT_PUBLIC_AXICMS_RPC_URL=http://localhost:8545
NEXT_PUBLIC_AXICMS_CHAIN_ID=2177282315500993
NEXT_PUBLIC_AXICMS_REST_URL=http://localhost:1317

# Deployed contract addresses
NEXT_PUBLIC_GAME_CONTRACT_ADDRESS=0x90DB0A1790D5CAA481052DbbF7b1F201ab8f0387
NEXT_PUBLIC_PLAYER_TOKEN_ADDRESS=0xA3C35867060244534F51A92a205496003cC4592E

# Server-side keys (not exposed to the browser)
DEPLOYER_PRIVATE_KEY=<your-deployer-private-key>
API_FOOTBALL_KEY=<your-api-sports-io-key>

Step 4 — Add axi-cms to MetaMask

Open MetaMask → Settings → Networks → Add a network manually:

Field Value
Network Name Initia axi-cms
RPC URL http://localhost:8545
Chain ID 2177282315500993
Currency Symbol GAS

MetaMask will switch to this network automatically when you connect on the site, but adding it manually first avoids a prompt on first load.


Step 5 — Deploy Contracts (first time only)

If contracts are already deployed and addresses match the ones in .env.local, skip this step.

cd contracts

# 1. Core game contract + reference player token
npx hardhat run scripts/deploy.ts --network axicms

# 2. All 19 club fan tokens (PAR, MON, MAR, LYO, LIL, LEN, NIC, REN…)
npx hardhat run scripts/deploy-team-tokens.ts --network axicms

# 3. MerchNFT + TicketContract with fan token payment integration
npx hardhat run scripts/deploy-merch-tickets-v2.ts --network axicms

Deployed addresses are printed to the console and saved to contracts/deployments/axicms.json.


Step 6 — Seed On-Chain Data (first time only)

Start the frontend first, then call the seed endpoints once to populate merch items and ticket fixtures on-chain:

# In one terminal — start the frontend
cd web && npm run dev

# In another terminal — seed data (run once only)
curl -X POST http://localhost:3000/api/merch/seed
curl -X POST http://localhost:3000/api/tickets/seed

You should see success responses listing the seeded items. After this, the marketplace and tickets pages will show live on-chain inventory.


Step 7 — Open the App

Navigate to http://localhost:3000.

First-time checklist:

  • MetaMask is installed and unlocked
  • axi-cms network is selected in MetaMask (or will be prompted on connect)
  • Click Connect Wallet — your .init username appears in the nav if you have one registered
  • Click Faucet to get player tokens for testing
  • Click Bridge to open the Interwoven bridge modal (connects to Initia testnet)

Game Flow

Player A                          Player B
   │                                  │
   │  1. Select 5 player tokens       │
   │  2. POST /api/game/create        │
   │  3. Approve + createGame()       │
   │     on-chain via MetaMask        │
   │  4. Receive gameCode ──────────► │
   │                                  │  5. Enter gameCode
   │                                  │  6. Select 5 player tokens
   │                                  │  7. Approve + joinGame()
   │                                  │     on-chain via MetaMask
   │                                  │
   │  ◄──────── Winner Determined ───►│
   │         (on-chain resolution)    │
   │                                  │
   └── Winner receives all 10 token positions ──┘

Purchase Flow (Merch / Tickets)

User
 │
 ├─ 1. Connect MetaMask to axi-cms
 ├─ 2. Check balance of required club fan token
 ├─ 3. (if needed) Click faucet → POST /api/club-faucet → 1,000 tokens minted
 ├─ 4. MetaMask: approve(MerchNFT/TicketContract, price)
 ├─ 5. MetaMask: buyMerch(id) / buyTicket(matchId)
 └─ 6. NFT / ticket event logged → visible in /claim profile

Profile Tabs

The /claim page shows four tabs for a connected wallet:

Tab Data Source
Player Tokens /api/tokens — live balances from axi-cms
Merchandise ERC-721 Transfer event logs on MerchNFT contract
My Tickets TicketPurchased event logs on TicketContract
Club Tokens balanceOf calls across all 19 FanToken contracts

Deployed Contracts (axi-cms)

Contract Address
GameContractMultiToken 0x90DB0A1790D5CAA481052DbbF7b1F201ab8f0387
MerchNFT 0x18336FF9391De2fFBDe32B4c37d57e86801E3188
TicketContract 0x97A9D1E4f3861acda77F084803Edf0da49C1bC90
PAR (PSG Fan Token) 0x69996CBeFF6aC7CdE6e9820Ac9221B29d7D89dA8
MON (Monaco Fan Token) 0xC25184Ba7A8cA4a146A350d8895bDA75FDA3973e
MAR (Marseille Fan Token) 0x45563BA6e572284389F0D8FfD03eD677dB36C3eC
LYO (Lyon Fan Token) 0x52e7d0669832Be8d0cfcF8AD74cf4E46BBb1050A
LIL (Lille Fan Token) 0x93376EE5cf615CC68D9C8E32d9927Ac559BcC1BB
LEN (Lens Fan Token) 0x79d28F02977632c184d50D5b8c75c610bf970105
NIC (Nice Fan Token) 0x8E925a17e4661dF282CfB1Be49578b264E2070c9
REN (Rennes Fan Token) 0x3863Ce8FF53d0EaA69340f4B0B143987fD885f0b

Running on Your Own Rollup

StormX is hardcoded to talk to the axi-cms rollup. If you want to spin up a fresh rollup and wire the project to it, follow these steps.

1. Create an Initia Rollup

Go to app.initia.xyz/rollup/create and create a new Interwoven Rollup with the following settings:

Field Recommended value
VM Type EVM
Gas Token Your own (e.g. GAS)
Gas Price 0 (gasless)

Once created, the dashboard gives you:

  • RPC URL — e.g. https://rpc.your-rollup.initia.xyz
  • Chain ID — a unique integer, e.g. 1234567890
  • Deployer private key — fund it from the faucet on the dashboard

2. Update Hardhat Config

Open contracts/hardhat.config.ts and update the axicms network block:

axicms: {
  url: "https://rpc.your-rollup.initia.xyz",
  chainId: 1234567890,          // your rollup's chain ID
  accounts: [process.env.PRIVATE_KEY!],
  gasPrice: 0,
}

3. Deploy All Contracts

cd contracts

# Set your deployer key
export PRIVATE_KEY=0x<your-deployer-key>

# Deploy game contract + a reference player token
npx hardhat run scripts/deploy.ts --network axicms

# Deploy all 19 club fan tokens
npx hardhat run scripts/deploy-team-tokens.ts --network axicms

# Deploy MerchNFT + TicketContract
npx hardhat run scripts/deploy-merch-tickets-v2.ts --network axicms

Each script prints deployed addresses. They are also saved to contracts/deployments/axicms.json.

4. Update Contract Addresses in the Frontend

After deployment, update these files with the new addresses:

web/.env.local

NEXT_PUBLIC_AXICMS_RPC_URL=https://rpc.your-rollup.initia.xyz
NEXT_PUBLIC_AXICMS_CHAIN_ID=1234567890
NEXT_PUBLIC_GAME_CONTRACT_ADDRESS=<new GameContractMultiToken address>
DEPLOYER_PRIVATE_KEY=<your-deployer-key>

web/lib/contract-config.ts — update GAME_CONTRACT_ADDRESS.

web/lib/const.ts — update MerchContractAddress, TicketingContractAddress, and the teamFanTokens map (one address per club symbol).

web/lib/merch.ts — update paymentTokenAddress for each merch item to match the newly deployed club fan token addresses.

web/app/tickets/page.tsx — update paymentTokenAddress inside FIXTURE_META for each fixture.

5. Update the viem Chain Definition

Open web/lib/client.ts and update the chain object:

export const axicms = defineChain({
  id: 1234567890,                          // your chain ID
  name: "My Rollup",
  rpcUrls: {
    default: { http: ["https://rpc.your-rollup.initia.xyz"] },
  },
  nativeCurrency: { name: "GAS", symbol: "GAS", decimals: 18 },
});

6. Add MetaMask Network

Field Value
Network Name Your rollup name
RPC URL https://rpc.your-rollup.initia.xyz
Chain ID 1234567890
Currency Symbol GAS

7. Seed Initial Data

With the frontend running (npm run dev), call the seed endpoints once to populate merch items and ticket fixtures on-chain:

curl -X POST http://localhost:3000/api/merch/seed
curl -X POST http://localhost:3000/api/tickets/seed

After this the marketplace and ticket pages will show live on-chain inventory.


Project Structure

StormX/
├── web/                          # Next.js frontend
│   ├── app/
│   │   ├── page.tsx              # Landing page (3D scene + features)
│   │   ├── game/                 # Game create, join, battle, history
│   │   ├── marketplace/          # Merchandise NFT store
│   │   ├── tickets/              # Match ticket listings
│   │   ├── claim/                # Player profile (tokens, NFTs, tickets)
│   │   ├── leagues/              # Ligue 1 standings + team browser
│   │   ├── player/               # Individual player pages
│   │   ├── team/                 # Team squad pages
│   │   ├── components/
│   │   │   ├── ConnectButton.tsx # Wallet button — shows .init username
│   │   │   ├── BridgeButton.tsx  # Interwoven Bridge modal trigger
│   │   │   └── Provider.tsx      # InterwovenKit + Wagmi + TESTNET preset
│   │   └── api/                  # 14 Next.js API routes
│   ├── lib/
│   │   ├── client.ts             # viem chain + wallet client
│   │   ├── const.ts              # ABI definitions + contract addresses
│   │   ├── contract-config.ts    # Game contract config
│   │   ├── merch.ts              # Merch display metadata
│   │   └── contractReaders.ts    # Read helpers
│   └── docs/
│       ├── API_REFERENCE.md
│       └── API_CONTRACT.md
└── contracts/
    ├── contracts/
    │   ├── Game.sol              # GameContractMultiToken
    │   ├── PlayerToken.sol       # Per-player ERC-20
    │   ├── FanToken.sol          # Club ERC-20 (18 decimals)
    │   ├── Merch.sol             # MerchNFT (ERC-721)
    │   ├── Ticketing.sol         # TicketContract
    │   └── SimpleRandom.sol      # Block-based randomness
    ├── scripts/
    │   ├── deploy.ts             # Core contract deployment
    │   ├── deploy-team-tokens.ts # 19 club fan tokens
    │   └── deploy-merch-tickets-v2.ts # Merch + ticket contracts
    └── deployments/
        └── axicms.json           # Deployed addresses

About

A fully on-chain football player tokenization platform built on the Initia axi-cms rollup.

Resources

Stars

Watchers

Forks

Contributors