Web3 music marketplace. Cross-chain payments, seller payment choice. Decentralized on Base.
GotMusic is a decentralized music marketplace that revolutionizes how music producers monetize their work and how fans discover new music. Built for ETHOnline 2025, it combines cutting-edge blockchain technology with professional audio quality to create a Web3-native platform that bridges the gap between traditional music streaming and decentralized ownership.
At its core, GotMusic functions like "Spotify meets OpenSea for music producers."
Producers upload their beats, samples, and tracks as high-quality WAV or AIFF files, which are then encrypted and stored on IPFS (InterPlanetary File System) for true decentralization. Each upload creates an immutable blockchain attestation using Ethereum's Attestation Service (EAS), providing verifiable provenance and ownership records.
Buyers can browse the catalog, listen to 30-second previews without needing a wallet, and purchase full tracks using various cryptocurrencies including PYUSD (PayPal's stablecoin), USDC, or ETH across multiple blockchain networks.
The platform's blockchain infrastructure is built on a comprehensive EAS attestation system with five deployed resolver contracts on Base Mainnet:
Content Upload Resolver (0x166782ffD3D0Cf4A48b2bf4FC9929ED5374e2052)
0xa4f0cc13cdd748ca464f3a68dc543c94a33662b3f4f3cc3a63246e654e98b4e6License Receipt Resolver (0x2de43c7d4C4F5602EF538E85C0d8D78e50A41D18)
0xa6bedff8a7aeff07860da391aaed576f47982f35e9119f5e3c2facbb07417728Vendor Status Resolver (0xD64BA17E7a25E8F5b8da04AB5e64Ac7e5C0bef23)
0x96c49253f6c997eca8dcf8798f22ad9e22f465b6f2e58aaf810f835c582b2164Content Flag Resolver (0xC994f36B9de94Fc751122b196BeBC27D7e9A90f4)
0x59354c5ee8315da7dadc048be90d60ad31412284213fcb3b4c807fcbdb24b6c9Wallet Verification Resolver (0xd8FFCfB067F0b4EeeFC06ac74598e537b421a9A4)
0x9ba441e691610a3ee33b88d78410a208dbcbaae9affed65b9eb546efaa5a497bThe platform's core infrastructure is powered by essential Web3 APIs and services:
Lit Protocol - Decentralized access control and encryption
Lighthouse + IPFS - Decentralized file storage
Avail Nexus - Cross-chain routing and liquidity
Blockscout - Blockchain explorer and analytics
PYUSD Integration - PayPal's stablecoin payments
0x6c3ea9036406852006290770bedfcaba0e23a0e8 (Ethereum Mainnet)What sets GotMusic apart is its professional-grade audio processing pipeline that includes LUFS normalization (following Spotify's -14 dB standard), multiple quality tiers for different use cases, and real-time waveform visualization.
The platform supports both web and mobile applications, with a React Native mobile app that includes biometric authentication, passkey integration, and secure local storage. Every aspect of the platform is designed with decentralization in mind, from the IPFS storage for music files to the EAS attestations for licensing and provenance, creating a truly Web3-native music ecosystem that gives artists more control over their work and fans true ownership of their purchases.
"Spotify meets OpenSea for music producers" - A Web3 music marketplace with:
A comprehensive Web3 music platform with:
Monorepo Structure (Turbo + Yarn 4.3.1)
apps/
web/ # Next.js 16 + React 19 + TypeScript
mobile/ # React Native + Expo 53 + NativeWind
worker/ # Background processing worker
packages/
ui/ # Shared Radix UI components + Design tokens
api/ # TypeScript API client + Zod validation
tokens/ # Design system tokens (Style Dictionary)
fixtures/ # Test data and mocks
Database Layer (Drizzle ORM + PostgreSQL)
// Schema Definition
export const assetsPg = pgTable("assets", {
id: pgText("id").primaryKey(),
title: pgText("title").notNull(),
description: pgText("description"),
price: pgDecimal("price", { precision: 18, scale: 6 }),
currency: pgText("currency").notNull().default("PYUSD"),
status: assetStatusEnum("status").notNull().default("draft"),
createdAt: pgTimestamp("created_at").notNull().defaultNow(),
});
export const assetFilesPg = pgTable("asset_files", {
id: pgText("id").primaryKey(),
assetId: pgText("asset_id").notNull(),
kind: assetFileKindEnum("kind").notNull(), // original, preview, artwork, waveform
storageKey: pgText("storage_key").notNull(),
ipfsCid: pgText("ipfs_cid"),
ipfsGatewayUrl: pgText("ipfs_gateway_url"),
bytes: pgInteger("bytes"),
mime: pgText("mime"),
checksum: pgText("checksum"),
});
EAS Attestation System (Base Mainnet)
// ContentUploadResolver.sol
contract ContentUploadResolver {
function onAttest(
IEASLike.Attestation calldata att,
uint256 value
) external payable onlyEAS returns (bool) {
(
string memory assetId,
string memory fileHash,
string memory contentType,
uint256 fileSize,
string memory title,
string memory license_,
bool original
) = abi.decode(att.data, (string, string, string, uint256, string, string, bool));
// Validation and state management
bytes32 assetKey = keccak256(bytes(assetId));
require(!_uploadedAssets[assetKey], "Asset already uploaded");
_uploadedAssets[assetKey] = true;
_uploaderCount[att.attester] += 1;
emit ContentUploaded(att.attester, assetId, fileHash, block.timestamp);
return true;
}
}
Multi-Chain Payment Architecture
// PYUSD Integration (Ethereum Mainnet)
const PYUSD_CONTRACT_ADDRESS = "0x6c3ea9036406852006290770bedfcaba0e23a0e8";
const PYUSD_ABI = [
"function balanceOf(address owner) view returns (uint256)",
"function transfer(address to, uint256 amount) returns (bool)",
"function approve(address spender, uint256 amount) returns (bool)"
];
// Avail Nexus Cross-Chain Routing
import { NexusClient } from "@avail-project/nexus-core";
const nexusClient = new NexusClient({
network: "mainnet",
rpcUrl: process.env.AVAIL_RPC_URL
});
LUFS Normalization Engine
class LUFSNormalizer {
private targetLUFS = -14.0; // Spotify standard
async normalizeAudio(audioBuffer: AudioBuffer): Promise<AudioBuffer> {
const lufs = await this.measureLUFS(audioBuffer);
const gainReduction = this.targetLUFS - lufs;
const gainMultiplier = Math.pow(10, gainReduction / 20);
return this.applyGain(audioBuffer, gainMultiplier);
}
private async measureLUFS(buffer: AudioBuffer): Promise<number> {
// Implement EBU R128 loudness measurement
const samples = buffer.getChannelData(0);
const rms = this.calculateRMS(samples);
return 20 * Math.log10(rms) - 0.691; // Convert to LUFS
}
}
Quality Tier Management
interface QualityTier {
name: 'preview' | 'streaming' | 'download' | 'master';
bitrate: number;
format: 'AAC' | 'FLAC' | 'WAV';
lufs: number;
useCase: string;
}
const QUALITY_TIERS: QualityTier[] = [
{ name: 'preview', bitrate: 128, format: 'AAC', lufs: -14, useCase: '30s previews' },
{ name: 'streaming', bitrate: 320, format: 'AAC', lufs: -14, useCase: 'Full playback' },
{ name: 'download', bitrate: 1411, format: 'FLAC', lufs: -14, useCase: 'Licensed content' },
{ name: 'master', bitrate: 2304, format: 'WAV', lufs: -14, useCase: 'Studio quality' }
];
Lit Protocol Integration
// Lit Protocol Service
export class LitProtocolService {
private litNodeClient: LitNodeClient;
async encryptAsset(
file: File,
accessControlConditions: AccessControlConditions
): Promise<EncryptionResult> {
const authSig = await this.getAuthSig();
const { ciphertext, dataToEncryptHash } = await encryptString({
authSig,
accessControlConditions,
chain: "base",
dataToEncrypt: await file.text(),
});
return {
ciphertext,
dataToEncryptHash,
accessControlConditions,
};
}
async decryptAsset(
ciphertext: string,
dataToEncryptHash: string,
accessControlConditions: AccessControlConditions
): Promise<string> {
const authSig = await this.getAuthSig();
return await decryptToString({
authSig,
accessControlConditions,
chain: "base",
ciphertext,
dataToEncryptHash,
});
}
}
Hybrid IPFS + CDN Strategy
// Storage Service
export class StorageService {
private pinata: PinataSDK;
private r2: R2Client;
async uploadFile(file: File): Promise<StorageResult> {
// 1. Upload to IPFS via Pinata
const ipfsResult = await this.pinata.uploadFile(file);
// 2. Cache on R2 for performance
const r2Key = `cache/${ipfsResult.IpfsHash}`;
await this.r2.putObject(r2Key, file);
return {
ipfsHash: ipfsResult.IpfsHash,
ipfsUrl: `https://ipfs.io/ipfs/${ipfsResult.IpfsHash}`,
cacheUrl: `https://cdn.gotmusic.com/${r2Key}`,
gatewayUrl: `https://gateway.pinata.cloud/ipfs/${ipfsResult.IpfsHash}`
};
}
async getFile(hash: string): Promise<File> {
// Try cache first, fallback to IPFS
try {
return await this.r2.getObject(`cache/${hash}`);
} catch {
return await this.pinata.getFile(hash);
}
}
}
Web Player (Next.js 16 + Web Audio API)
// Web Audio Player
export class WebAudioPlayer {
private audioContext: AudioContext;
private audioBuffer: AudioBuffer | null = null;
private sourceNode: AudioBufferSourceNode | null = null;
async loadAudio(file: File): Promise<void> {
const arrayBuffer = await file.arrayBuffer();
this.audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer);
this.renderWaveform();
}
private renderWaveform(): void {
if (!this.audioBuffer) return;
const canvas = document.getElementById('waveform') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;
const data = this.audioBuffer.getChannelData(0);
// Render waveform visualization
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#00ff88';
ctx.lineWidth = 2;
ctx.beginPath();
for (let i = 0; i < data.length; i += 100) {
const x = (i / data.length) * canvas.width;
const y = (data[i] * canvas.height / 2) + canvas.height / 2;
ctx.lineTo(x, y);
}
ctx.stroke();
}
}
Mobile Player (React Native + Expo Audio)
// Mobile Audio Player
export class MobileAudioPlayer {
private sound: Audio.Sound | null = null;
async loadAudio(uri: string): Promise<void> {
const { sound } = await Audio.Sound.createAsync(
{ uri },
{ shouldPlay: false, isLooping: false }
);
this.sound = sound;
}
async play(): Promise<void> {
if (this.sound) {
await this.sound.playAsync();
}
}
async seekTo(positionMillis: number): Promise<void> {
if (this.sound) {
await this.sound.setPositionAsync(positionMillis);
}
}
}
CI/CD Pipeline (GitHub Actions)
name: Build and Deploy
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
- run: corepack enable
- run: yarn install --immutable
- run: yarn tokens:build
- run: yarn build
- run: yarn typecheck
- run: yarn lint
- run: yarn test
Testing Strategy
// E2E Tests (Playwright)
test('user can browse and purchase music', async ({ page }) => {
await page.goto('/catalog');
await page.click('[data-testid="asset-card"]');
await page.click('[data-testid="purchase-button"]');
await page.fill('[data-testid="wallet-address"]', '0x...');
await page.click('[data-testid="confirm-purchase"]');
await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
});
// Unit Tests (Jest)
describe('LUFSNormalizer', () => {
it('should normalize audio to -14 LUFS', async () => {
const normalizer = new LUFSNormalizer();
const audioBuffer = createTestAudioBuffer();
const normalized = await normalizer.normalizeAudio(audioBuffer);
const lufs = await normalizer.measureLUFS(normalized);
expect(lufs).toBeCloseTo(-14, 1);
});
});
The bottom line: We built a solid foundation for a Web3 music marketplace, with some scope adjustments needed for the hackathon timeline. The core vision remains strong, and we have a clear path forward.

