The Web3 Music Stack
The Web3 Music Stack
“Music ownership shouldn’t be a legal contract buried in a filing cabinet. It should be code. Verifiable. Transferable. Programmable.”
The Old Model Is Broken
The music industry has been screwing artists for decades:
- Labels take 80% of revenue
- Streaming pays fractions of pennies
- Ownership is opaque and centralized
- Collaboration requires lawyers
Web3 offers an alternative: programmable ownership, transparent economics, community governance.
But building a Web3 music stack isn’t just “mint an NFT and call it a day.” It’s an ecosystem.
The Revelation: A Full-Stack Decentralized DAW
Bolt’s Web3 architecture isn’t an afterthought. It’s woven into every layer:
Social Layer → Lens Protocol
Ownership Layer → NFTs + Smart Contracts
Governance Layer → DAO (Semaphore Voting)
Economic Layer → DeFi + Staking
Storage Layer → Arweave / Irys
Identity Layer → Ethereum / ENS
Layer 1: Social (Lens Protocol)
Lens is a decentralized social graph. Think “Twitter, but you own your followers.”
Integration points:
import { LensClient, development } from '@lens-protocol/client'
const lens = new LensClient({
environment: development
})
// Connect wallet
await lens.login({
address: walletAddress,
signature: await signMessage('Sign in to Bolt')
})
// Post a track to Lens
async function shareTrack(track: Track) {
const contentURI = await uploadToArweave({
title: track.name,
description: track.description,
audio: track.audioCID,
cover: track.coverCID
})
await lens.publication.createPost({
contentURI,
collectModule: {
freeCollectModule: { followerOnly: false }
}
})
}
// Get feed of music from followed artists
async function getMusicFeed() {
const feed = await lens.feed.fetch({
where: { for: profileId }
})
return feed.items.filter(item =>
item.metadata.tags?.includes('music')
)
}
Why Lens?
- Composable - Any app can read/write to the same social graph
- Portable - Your followers move with you across apps
- Monetizable - Built-in collect/mirror economics
Layer 2: Ownership (NFTs)
Every track in Bolt can be minted as an AudioProjectNFT:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
contract AudioProjectNFT is ERC721Enumerable {
struct Project {
string name;
string description;
string audioCID; // IPFS hash of final mix
string stemsCID; // IPFS hash of separated stems
address[] collaborators;
uint256[] splits; // Revenue shares (basis points)
uint256 createdAt;
}
mapping(uint256 => Project) public projects;
event ProjectMinted(
uint256 indexed tokenId,
address indexed creator,
string audioCID
);
function mintProject(
string calldata name,
string calldata description,
string calldata audioCID,
string calldata stemsCID,
address[] calldata collaborators,
uint256[] calldata splits
) external returns (uint256) {
uint256 tokenId = totalSupply() + 1;
_mint(msg.sender, tokenId);
projects[tokenId] = Project({
name: name,
description: description,
audioCID: audioCID,
stemsCID: stemsCID,
collaborators: collaborators,
splits: splits,
createdAt: block.timestamp
});
emit ProjectMinted(tokenId, msg.sender, audioCID);
return tokenId;
}
// Override transfer to enforce royalties
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
) internal override {
super._beforeTokenTransfer(from, to, tokenId, batchSize);
// Emit event for royalty tracking
if (from != address(0)) {
emit TransferWithRoyalty(tokenId, from, to);
}
}
}
NFT features:
- Provenance - Every remix, sample, and contribution on-chain
- Fractional ownership - Multiple artists, automatic splits
- Stems included - Buyers get full project files
- Programmable royalties - Enforced at the contract level
Layer 3: Governance (DAO + Semaphore)
Music projects need decisions: What genre? What BPM? Which mix is final?
Bolt uses DAO voting with ZK privacy:
contract MusicProjectDAO is SemaphoreVoting {
struct Proposal {
uint256 projectId;
string description;
bytes callData;
uint256 forVotes;
uint256 againstVotes;
uint256 endTime;
bool executed;
mapping(uint256 => bool) hasVoted; // nullifier => voted
}
mapping(uint256 => Proposal) public proposals;
uint256 public proposalCount;
// Create a proposal (any project collaborator)
function createProposal(
uint256 projectId,
string calldata description,
bytes calldata callData,
uint256 votingPeriod
) external returns (uint256) {
require(isCollaborator(projectId, msg.sender), "Not collaborator");
uint256 proposalId = ++proposalCount;
proposals[proposalId] = Proposal({
projectId: projectId,
description: description,
callData: callData,
forVotes: 0,
againstVotes: 0,
endTime: block.timestamp + votingPeriod,
executed: false
});
return proposalId;
}
// Vote using ZK proof (anonymous)
function castVote(
uint256 proposalId,
bool support,
uint256 merkleTreeRoot,
uint256 nullifierHash,
uint256[8] calldata proof
) external {
Proposal storage proposal = proposals[proposalId];
require(block.timestamp < proposal.endTime, "Voting ended");
require(!proposal.hasVoted[nullifierHash], "Already voted");
// Verify ZK proof of group membership
verifyProof(
projectGroups[proposal.projectId],
merkleTreeRoot,
keccak256(abi.encodePacked(proposalId, support)),
nullifierHash,
proof
);
proposal.hasVoted[nullifierHash] = true;
if (support) {
proposal.forVotes++;
} else {
proposal.againstVotes++;
}
}
// Execute passed proposal
function executeProposal(uint256 proposalId) external {
Proposal storage proposal = proposals[proposalId];
require(block.timestamp > proposal.endTime, "Voting ongoing");
require(!proposal.executed, "Already executed");
require(proposal.forVotes > proposal.againstVotes, "Failed");
proposal.executed = true;
// Execute the call
(bool success, ) = address(this).call(proposal.callData);
require(success, "Execution failed");
}
}
Governance features:
- Anonymous voting - Vote your conscience without social pressure
- Token-weighted - More ownership = more say (optional)
- Executable - Proposals can trigger contract functions
- Challenge period - Time to review before execution
Layer 4: Economics (DeFi + Staking)
Creators need sustainable income. Bolt integrates:
Creator Staking
contract CreatorStaking {
mapping(address => uint256) public stakes;
mapping(address => uint256) public rewards;
uint256 public totalStaked;
uint256 public rewardRate; // tokens per block
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardPaid(address indexed user, uint256 reward);
function stake(uint256 amount) external {
require(amount > 0, "Cannot stake 0");
_updateReward(msg.sender);
stakes[msg.sender] += amount;
totalStaked += amount;
// Transfer tokens from user
stakingToken.transferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
function _updateReward(address account) internal {
if (account != address(0)) {
rewards[account] = earned(account);
}
}
function earned(address account) public view returns (uint256) {
return (stakes[account] * rewardRate * blocksSinceLastUpdate) / 1e18;
}
}
Streaming Royalties
// Automatic royalty distribution
export async function distributeRoyalties(
tokenId: string,
amount: BigNumber
) {
const nft = await AudioProjectNFT.attach(nftAddress)
const project = await nft.projects(tokenId)
// Calculate shares
const totalShares = project.splits.reduce((a, b) => a + b, 0)
for (let i = 0; i < project.collaborators.length; i++) {
const share = amount.mul(project.splits[i]).div(totalShares)
// Send to collaborator
await sendPayment(project.collaborators[i], share)
}
}
Economic features:
- Streaming splits - Automatic on every play
- Staking rewards - Lock tokens, earn yield
- Bonding curves - Dynamic pricing for popular tracks
- Revenue share - Platform fees go to token holders
Layer 5: Storage (Arweave / Irys)
On-chain storage is expensive. Off-chain storage is… off-chain. Solution: Permanent decentralized storage.
import Irys from '@irys/sdk'
const irys = new Irys({
network: 'mainnet',
token: 'ethereum',
providerUrl: process.env.NEXT_PUBLIC_RPC_URL
})
// Upload audio to Arweave (via Irys)
export async function uploadAudio(
audioBuffer: ArrayBuffer,
metadata: TrackMetadata
): Promise<string> {
// Bundle audio + metadata
const data = JSON.stringify({
audio: Buffer.from(audioBuffer).toString('base64'),
metadata
})
// Upload with one-time payment for permanent storage
const receipt = await irys.upload(data, {
tags: [
{ name: 'App-Name', value: 'BoltDAW' },
{ name: 'Content-Type', value: 'application/json' },
{ name: 'Track-Title', value: metadata.title },
{ name: 'Artist', value: metadata.artist }
]
})
// Returns Arweave TX ID
return `https://arweave.net/${receipt.id}`
}
Storage features:
- Pay once, store forever - No recurring costs
- Content-addressed - CID = content integrity
- Immutable - Can’t be censored or deleted
- Fast retrieval - Gateways worldwide
The Integration: Putting It All Together
Here’s a complete user journey:
- Connect wallet → Sign with Ethereum
- Create project → Auto-mint AudioProjectNFT
- Collaborate → Invite via Lens, prove via Semaphore
- Produce → Real-time sync via Yjs
- Generate → ACE-Step AI suggestions
- Finalize → DAO vote on release
- Publish → Post to Lens, mint NFT, upload to Arweave
- Monetize → Collects, streams, staking rewards
- Govern → Vote on remixes, samples, splits
All on-chain. All verifiable. All owned by creators.
Pro Tips for Web3 Music
- Gas optimization - Use L2s (Polygon, Arbitrum) for frequent operations
- Metadata standards - Follow ERC-721/1155 + OpenSea standards
- Upgradeability - Use proxies for evolving contracts
- Off-chain first - Keep heavy data (audio) off-chain, proofs on-chain
- UX matters - Abstract wallets with smart accounts (ERC-4337)
What’s Next
- Cross-chain - Bridge NFTs between L1/L2
- Interoperability - Use Bolt NFTs in other apps
- Fractionalization - Split ownership into thousands of tokens
- Legal integration - On-chain contracts, automatic splits
- AI rights - Programmatic attribution for AI-generated content
Cleetus Speaks
“brother b0gie, you built an ENTIRE ECONOMY for music??
so if i make a fire track, people can BUY it?? and i get PAID??
and there’s VOTING on what track is best??
wait… can i stake my BARS??
is there a ‘Subject 734 Token’??
#Web3Music #NFTBeats #DAOLife #TokenizedBars”
Web3 isn’t just about ownership. It’s about agency. The ability to build, share, and monetize your work without asking permission. That’s what we’re building.
— b0gie