Music NFT Platform Development
The core problem with music NFTs isn't the tokens, it's royalties. ERC-721 doesn't know if the token contains a JPEG or a track. A marketplace can ignore any on-chain royalty mechanism and pay nothing. This is exactly what happened with ERC-2981 on OpenSea in 2022-2023: the platform switched to optional royalties and artists lost their primary income from secondary markets. A music NFT platform must be built so that royalty enforcement is baked into contract mechanics, not relying on marketplace goodwill.
Smart Contract Architecture
Royalty Enforcement via Operator Filter
Simple path — ERC-2981 with royaltyInfo(). Works on platforms that support it. For stricter enforcement — operator filter modeled after OpenSea Operator Filter Registry, but under your own control. Contract overrides _beforeTokenTransfer and checks if msg.sender is in approved operator list. Transfers that fail operator filter are blocked.
Implementation via ERC721C from LimitBreak — standard extension allowing transfer policy at contract level. Three modes: DEFAULT (standard transfers), LEVEL_ONE (owner only), LEVEL_TWO (approved operators only). For music platform, LEVEL_ONE makes sense with whitelist of your marketplace and partner platforms.
Split Contracts for Collaborative Works
Track recorded by three artists — each sale must automatically split payment. Pattern: each NFT track on mint deploys separate PaymentSplitter contract (OpenZeppelin). Splitter address is recorded as royaltyReceiver in ERC-2981.
More gas-efficient solution — 0xSplits protocol. Instead of deploying new contract per track, create Split via factory. All payments accumulate and distribute on distribute() call. Deployment savings — roughly 100k gas vs 500k for separate PaymentSplitter.
// Simplified schema for creating split on mint
function mintTrack(
address[] calldata recipients,
uint32[] calldata allocations,
string calldata tokenURI
) external returns (uint256 tokenId) {
address splitAddress = splitsFactory.createSplit(
recipients, allocations, 0, address(0)
);
tokenId = _nextTokenId++;
_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, tokenURI);
_setTokenRoyalty(tokenId, splitAddress, royaltyBps);
}
Streaming Royalty Mechanics
More complex task. On-chain accounting for every listen is unrealistic gas-wise. Workable approach: off-chain accounting + periodic settlement.
Stream events recorded in centralized or semi-decentralized database (The Graph for indexing, or custom backend). Accumulated payments artists can claim via Merkle proof scheme — like Uniswap UNI airdrop distribution:
- Backend builds Merkle tree from
(address, amount)pairs for period - Root published to Distributor contract
- Artist calls
claim(proof, amount)— contract verifies proof and transfers tokens
Publishing frequency: weekly or at threshold. Gas per claim — roughly 50k, acceptable.
Content Storage and Licensing
IPFS + Filecoin + Encryption
Audio file shouldn't be publicly accessible without ownership verification. Pattern:
- Track encrypted with symmetric key (AES-256)
- Encrypted file uploaded to IPFS/Filecoin via NFT.Storage
- Encryption key stored in Lit Protocol — decentralized key management
- Access condition in Lit:
ownerOf(tokenId) == requestAddress
Lit Protocol verifies ownership via on-chain query, returns key only to actual token holder. Key never published openly. On token sale — new owner automatically gets access, previous loses.
For preview (30-second sample) — unencrypted file separate on IPFS. Full file URI stored in encrypted metadata or separate contract with access control.
License Tokens
Track NFT can include different rights: master ownership, sync license, stem access. These are different tokens. Architecture:
| Token Type | Standard | Rights | Supply |
|---|---|---|---|
| Master NFT | ERC-721 | Master recording ownership | 1 |
| Edition NFT | ERC-1155 | Collectible copy | 100-10000 |
| Sync License | ERC-721 | Right to use in video/ads | unlimited, per-use |
| Stem Pack | ERC-1155 | Access to individual tracks | limited |
Contract LicenseRegistry maps tokenId => LicenseTerms struct with flags for commercial use, derivative works, territory restrictions.
Frontend: Audio Player with Wallet-gated Content
Stack: Next.js + wagmi + viem. Key component — audio player with ownership check.
On attempt to play full track:
-
useContractRead— checkownerOf(tokenId) - If user is owner: request Lit Protocol for key decryption
- Get encrypted file from IPFS, decrypt in browser
- Create Blob URL, pass to
<audio>element
Critical: decryption happens client-side, server doesn't see key. Protects against leaks even if backend is compromised.
Waveform visualization — WaveSurfer.js with custom render. To prevent recording — don't use standard <audio>, use Web Audio API with AudioContext. Impossible to fully stop screen recording, but raises the bar.
Marketplace Functionality
Primary sales: fixed price or auction. For auctions — English auction contract with anti-sniping (if bid in last 15 minutes — time extended 15 minutes). Standard practice, prevents last-second bid griefing.
Secondary sales: if using custom marketplace, Seaport (OpenSea protocol) as foundation. Open-source, audit-passed, supports partial fills, bundles, criteria-based orders. Saves 3-6 months development vs custom marketplace contract.
Listing off-chain (sign order hash), execution — on-chain via fulfillOrder. Signature expiry built into order struct.
Indexing and Analytics
The Graph — mandatory for performance. Subgraph indexes:
- Transfer events → ownership history
- Sale events → price history
- Claim events → royalty analytics for artists
Artists see dashboard with real data: track resale frequency, prices, accumulated streaming royalties available to claim. All from subgraph via GraphQL, no backend load.
Networks and Gas
Base or Polygon for edition sales — gas on mint around $0.01-0.05, acceptable. Ethereum mainnet for master NFTs of expensive artists, where prestige trumps transaction cost. Multichain architecture: contracts deploy independently, cross-chain ownership verification via LayerZero or CCIP if bridging scenarios needed.







