# Open Audio Protocol — Complete Documentation
> All documentation for the Open Audio Protocol in a single file.
> Source: https://docs.audius.co
---
> Source: https://docs.audius.co/
# Open Audio Protocol
The **Open Audio Protocol** exists to bring the world's music onchain. It's a community-run, transparent, and open-source repository known as the
**Global Music Database**.
Originally pioneered in the [2020 Audius Whitepaper](https://whitepaper.audius.co), the Open Audio Protocol marks the next chapter for a music ecosystem powered by `$AUDIO`. The protocol combines blockchain, crypto-economics, and music industry technology standards to deliver new tools for distribution, access, and direct-to-fan freedom.
* **Developers:** you've found a backend for your music app or DSP
* **Artists:** you've found storage and distribution for your works
* **Infra Providers:** you've found opportunity to earn staking rewards by securing the catalog
The docs ahead cover the core concepts and tutorials needed to get started building with the protocol.
Start listening or sign up on [Audius](https://audius.co).
---
> Source: https://docs.audius.co/concepts/audio

# \$AUDIO
Solana (SPL) address: [9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM](https://solscan.io/account/9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM)\
Ethereum (ERC) address: [0x18aAA7115705e8be94bfFEBDE57Af9BFc265B998](https://etherscan.io/token/0x18aAA7115705e8be94bfFEBDE57Af9BFc265B998)
\$AUDIO secures and governs the Open Audio Protocol.
It is the fundamental unit of cryptocurrency that powers artist coins, rewards, staking, governance, and all derivative features of the Open Audio Protocol. Through its existence, \$AUDIO offers a way for independent actors in a distributed community (technologists, artist, fans or some combination of the three) to act with aligned incentives defend the open catalog of music that exists in the global music database.
\$AUDIO launched October 22, 2020 with a fixed genesis supply of 1,000,000,000.
## Security
\$AUDIO secures the protocol through staking. \$AUDIO is staked as collateral to operate a share of the network, storing and streaming media back to end-users. In turn, staked \$AUDIO earns ongoing issuance through rewards, but also risks penalty through an automated slashing mechanism.
## Governance
Staking \$AUDIO gives users governance weight to influence the future of the protocol, with each token staked equaling one vote. Stakers of \$AUDIO also govern the global fee pool earned by the network, which captures 10% of all sales, and 1% of all Artist Coin trading fees.
## Rewards
* A 7% per annum reward rate is distributed on a weekly basis to the stakers of the network.
* A 1% per annum reward rate is distributed through a decentralized rewards system to participants of the protocol.
Ongoing issuance is modifiable by governance.
## Access
Utilizing \$AUDIO, developer applications, such as [Audius](https://audius.co) are able to offer unique features and experiences to holders and stakers of \$AUDIO throughout their applications.
---
Together the holders and users of \$AUDIO power an entirely community-hosted music database. Those who own \$AUDIO own the protocol.
---
> Source: https://docs.audius.co/concepts/staking
#
Staking
There are two ways to stake \$AUDIO:
- Directly staking through the [Staking.sol](https://etherscan.io/address/0xe6D97B2099F142513be7A2a068bE040656Ae4591) contract by registering a `validator node`
- Delegating \$AUDIO stake through the [DelegateManager.sol](https://etherscan.io/address/0x4d7968ebfD390D5E7926Cb3587C39eFf2F9FB225) contract to an existing `validator` (node operator) of the protocol.
This way, anyone can stake \$AUDIO, no matter how much or how little.
\$AUDIO stakers earn a share of the protocol's 7% annual reward rate, distributed proportionally across all staked tokens. Rewards are claimable weekly and can be claimed by any staker or delegator, ensuring a fully permissionless process that prevents deadlocks.
Staking is instant, while unstaking requires a 7-day cooldown, allowing governance proposals to complete before funds are withdrawn.
## Direct Staking
Operating a `validator node` requires a minimum stake of 200,000 \$AUDIO, with a maximum bond limit of 15,000,000 \$AUDIO per node. Stake is held at the `validator` account level and is spread across all nodes that they operate.
For example, if a `validator` themselves is staking 400,000 \$AUDIO and receiving a delegation of 700,000 \$AUDIO, they may run between 1 and 5 nodes. If a `validator` is staking 1,000,001 \$AUDIO and receiving 14,000,000 in delegation, they must run at least 2 nodes.
`Validators` choose the parameters to receive delegation:
- The percentage fee of rewards they capture from all delegated \$AUDIO
- The minimum amount of stake an individual party is permitted to delegate to them
## Delegate Staking
Users may wish to provide economic security to the protocol, but be unable to provide hardware directly themselves to operate a node. Therefore, they may choose to delegate stake to another existing validator on the network.
Delegators must satisfy the minimum delegation amount set by their chosen validator and share in any reductions in stake resulting from slashing or penalties due to validator negligence.
Delegator rewards are earned and claimed in the same transactions as when the validator they are staked to claims rewards.
## Slashing
Negligent validators whose nodes do not meet their obligations can be slashed.
Slashing reduces a validator's staked \$AUDIO when peformed. The penalty is applied by calling **`slash`** on the [DelegateManager](https://etherscan.io/address/0x4d7968ebfD390D5E7926Cb3587C39eFf2F9FB225#code) contract. Slashed tokens are burned (sent to the null address).
On-chain slashing occurs only after a [governance](/concepts/governance) proposal passes a vote and someone executes the proposal's calldata. Coordination (uptime, storage proofs, jailing on the Open Audio coordination layer) is described under [SLA proofs](/concepts/validators#sla-proofs); those mechanisms inform when the community might propose a slash.
### Proposal calldata
Governance proposals encode a target contract, function signature, and ABI-encoded arguments. For `slash`, the first argument is the **amount in wei** (1 wei = 10⁻¹⁸ \$AUDIO; \$AUDIO uses 18 decimals, like ETH). The second argument is the **validator service provider address** to slash.
You can draft and submit proposals through the [Open Audio Staking UI](https://staking.openaudio.org/#/governance) or the [Audius Protocol Dashboard](https://dashboard.audius.org).
#### Example
| Field | Value |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Target contract | `0x4d7968ebfD390D5E7926Cb3587C39eFf2F9FB225` ([DelegateManager](https://etherscan.io/address/0x4d7968ebfD390D5E7926Cb3587C39eFf2F9FB225#code)) |
| Function | `slash(uint256,address)` |
| Arguments | `1000000000000000000`, `0x000000000000000000000000000000000000dEaD` |
`1000000000000000000` wei equals **1** \$AUDIO. The address `0x000000000000000000000000000000000000dEaD` is a **placeholder** for examples only; replace it with the real validator address from chain state when drafting an actual proposal.
Delegate stake is penalized alongside the validator's bond according to contract rules, proportional to the validator's stake; see the DelegateManager source and the [Direct staking](#direct-staking) / [Delegate staking](#delegate-staking) sections above for how validator vs. delegator stake is laid out.
Further details on automated mechanisms can be read in the [Validators](/concepts/validators#sla-proofs) section.
---
> Source: https://docs.audius.co/concepts/governance

# Governance
Governance is the process by which \$AUDIO token holders enact change to the Open Audio Protocol through onchain proposals.
Governance has the ability to control tokenomics of \$AUDIO, staking parameters, node software versions, and even modify the governance protocol itself.
The governance system can be viewed in several places
* Contract address on [etherscan](https://etherscan.io/address/0x4DEcA517D6817B6510798b7328F2314d3003AbAC#readProxyContract)
* Audius [protocol dashboard](https://staking.openaudio.org/#/governance)
* Official Graph Protocol [subgraph](https://thegraph.com/explorer/subgraphs/F8TjrYuTLohz64J8uuDke9htSR1aY9TGCuEjJVVjUJaD?view=Query&chain=arbitrum-one)
## Proposals
The governance proposal is intended to solicit community vote to modify onchain data stored in the governance system. Therefore, all governance proposals must act on chain data and lead to a specific outcome.
Every governance proposals has the following parameters:
| Parameter | Description | Example |
|---------------------|----------------------------------------------------------------|-----------------------------------------|
| `Proposer` | The address that submitted the proposal | 0x683A...68d7 |
| `Name` | The proposal's title | Add validator node type |
| `Description` | The proposal's long-form description | This proposals ... |
| `TargetContract` | The english-name registry key entry for the contract to act on | ServiceTypeManagerProxy |
| `FunctionSignature` | The method on the target contract to invoke | addServiceType(bytes32,uint256,unit256) |
| `CallData` | The proposal's long-form description | 0x76...,200000...,15000000... |
> The example proposal from this table can be viewed on the Audius [protocol dashboard](https://staking.openaudio.org/#/governance/proposal/156)
Required software versions for the protocol are set by the governance system, in which case changelogs are typically used for proposal descriptions.
## Process
1. Forum post
2. Proposal
3. Discussion
4. Voting
5. Cooldown
6. Execution
A proposal has a 72 hour voting period once it is opened. After either passing or failing vote, the proposal stays inactive for 24 hours before it is able to be executed. Once the 24 hour cooldown is up, anyone can execute the proposal.
Voting quorum is 5% of the total \$AUDIO staked to the network, and proposals pass with 50% of the majority.
### Example
Using this
[governance proposal](https://staking.openaudio.org/#/governance/proposal/9) as an
example, you can see that different node operators and delegators voted in favor of extending the
voting time from 48 to 72 hours.
Given that the total number of votes \(1 AUDIO, 1 vote\) was above the quorum requirement of ~11M
\$AUDIO and the 50% majority \(100% voted in favor\) the proposal passed.
In doing so, the changes
[from this proposal](https://etherscan.io/tx/0xd4e14895b2a22b48469a43923ab7b30bee75f9a688941933430b3dae9510b8a6)
were
[executed through the governance contract](https://etherscan.io/tx/0x4396652fb9c1116cec5900f412608dfba7a3ec1b9967f4109a8ec3e09d3a75af),
changing the voting window from 48 hours to 72 hours.
---
> Source: https://docs.audius.co/concepts/validators

# Validators
Validators are the software infrastructure providers that keep the protocol running. They do this by running `validator nodes` in a connected network like many decentralized systems and blockchains.
A full list of current validators can be seen on the [protocol explorer](https://explorer.audius.engineering/validators).
To run a `validator node`, a minimum bond of 200,000 \$AUDIO is required to be put up as collateral. This earns the validator into a share of the protocol's 7% reward rate. In exchange, the validator is responsible for providing compute to handle its share of the protocol's traffic.
Implementations:
* go-openaudio: [code](https://github.com/openaudio/go-openaudio) [releases](https://hub.docker.com/r/openaudio/go-openaudio/tags)
## Solana & Ethereum
Under the hood, `validator nodes` are contextually aware of the Solana and Ethereum blockchains, which both provide underlying utility \$AUDIO. The node registration protocol currently lives on mainnet Ethereum.
## Versioning
The configuration for the `validator node` is managed through the [Service Type Manager](https://etherscan.io/address/0x9EfB0f4F38aFbb4b0984D00C126E97E21b8417C5) smart contract. The `validator node` software is versioned using Semver, whose versions are written into the onchain contract only through the Governance system after passing vote.
## Software
go-openaudio: [code](https://github.com/openaudio/go-openaudio) [releases](https://hub.docker.com/r/openaudio/go-openaudio/tags)
### Core
Core is the networking coordination layer between nodes, built on the [comet-bft](https://github.com/cometbft/cometbft) distributed state machine. It is used to keep `validator nodes` connected and in sync with each other. Core itself is not a blockchain in a traditional sense, but a byzantine fault-tolerant sync layer to coordinate metadata updates and streaming data. Its usage is very similar to that of [Celestia](https://celestia.org/) or role of comet-bft in the [Berachain](https://www.berachain.com/) project.
### Mediorum
Mediorum is the file storage system that underpins all media (resources) stored on the protocol. Mediorum accepts file uploads, validates their contents, transcodes content into streamable artifacts, and provides content-addressable identifiers for files using the [go-cid](github.com/ipfs/go-cid) library from [IPFS](https://github.com/ipfs).
### SLA Proofs
`validator nodes` constantly police each other, determining whether they are satisfactorily serving their individual duty. All nodes can both propose when other nodes violate their commitments to the network and also provide attestations to other nodes when asked. The Uptime and Proof of Storage packages are what enforce the slashing components of the staking system.
### Rewards
The rewards module provides a system for `validator nodes` to attest to authority over rewards pools on mainnet blockchains like Solana, and distribute token supply to users (\$AUDIO or artist coins). Apps and users can deposit coins into reward pools that are then managed by the decentralized Open Audio Protocol.
---
> Source: https://docs.audius.co/concepts/wire-protocol

# Wire Protocol
The Open Audio wire protocol supports two message formats: **DDEX** (industry-standard release metadata) and **Entity Manager** (Audius-style entity CRUD). Both use the same consensus pipeline—messages flow through node RPC, are broadcast via gossip, proposed in blocks by validators, and committed after consensus.
---
## DDEX
The DDEX format is built on [DDEX](https://ddex.net/standards/), a widely-used music industry standard for distributing release information and reporting usage. OAP extends DDEX with cryptographic primitives like address keypairs and message signing to enable permissionless operation on a decentralized network.
The protocol is implemented in protobuf via [ddex-proto](https://github.com/OpenAudio/ddex-proto) and published to the [Buf Registry](https://buf.build/openaudio/ddex).
### Transaction Structure
All messages on OAP are wrapped in a transaction envelope that includes chain metadata and a signature provided by the sender. The `SignedTransaction` contains the serialized `Transaction` payload and it's accompanying signature. The transaction header includes chain ID, nonce, gas parameters, and the signer's address. The body uses a oneof field to support different message types.
```protobuf
message SignedTransaction {
bytes payload = 1;
bytes signature = 2;
}
message Transaction {
Header header = 1;
Body body = 2;
Footer footer = 3;
}
message Header {
uint64 chain_id = 1;
uint64 nonce = 2;
uint64 gas_limit = 3;
uint64 gas_price = 4;
uint64 deadline_height = 5;
string signer = 6;
}
message Body {
oneof action {
ElectronicReleaseNotification ern = 1;
PartyIdentificationAndEnrichment pie = 2;
MediaEnrichmentAndDescription mead = 3;
// Additional message types can be added here
}
}
message ElectronicReleaseNotification {
oneof msg {
ddex.ern.v381.NewReleaseMessage v381 = 1;
ddex.ern.v383.NewReleaseMessage v383 = 2;
ddex.ern.v42.NewReleaseMessage v42 = 3;
ddex.ern.v43.NewReleaseMessage v43 = 4;
ddex.ern.v432.NewReleaseMessage v432 = 5;
}
}
message PartyIdentificationAndEnrichment {
oneof msg {
ddex.pie.v10.PieMessage v10 = 1;
}
}
message MediaEnrichmentAndDescription {
oneof msg {
ddex.mead.v11.MeadMessage v11 = 1;
}
}
```
OAP accepts multiple DDEX specification versions and stores the protobuf messages as submitted. Indexers and applications can choose how to interpret and normalize the data for their use cases.
### Signing and Sending
The transaction header's `signer` field identifies who is submitting the message. This is how OAP determines ownership and verifies that the party submitting a release or enrichment is authorized to do so. Here's how to construct, sign, and send a transaction:
```go
"crypto/ecdsa"
"github.com/OpenAudio/go-openaudio/chain"
"github.com/OpenAudio/go-openaudio/pkg/common"
"github.com/OpenAudio/ddex-proto/gen/ddex/ern/v43"
"google.golang.org/protobuf/proto"
)
func signAndSend(privateKey *ecdsa.PrivateKey, ernMessage *v43.NewReleaseMessage) error {
// Derive the signer address from the private key
signerAddress := common.PrivKeyToAddress(privateKey)
// Create the transaction
tx := &chain.Transaction{
Header: &chain.Header{
ChainId: 1,
Nonce: 1,
GasLimit: 1000000,
GasPrice: 100,
DeadlineHeight: 12345,
Signer: signerAddress,
},
Body: &chain.Body{
Action: &chain.Body_Ern{
Ern: &chain.ElectronicReleaseNotification{
Msg: &chain.ElectronicReleaseNotification_V43{
V43: ernMessage,
},
},
},
},
}
// Serialize the transaction to get the payload
payload, err := proto.Marshal(tx)
if err != nil {
return err
}
// Sign the payload with your private key
signature, err := common.EthSign(privateKey, payload)
if err != nil {
return err
}
// Create the signed transaction
signedTx := &chain.SignedTransaction{
Payload: payload,
Signature: signature,
}
// Submit to the network via RPC or gossip
return submitTransaction(signedTx)
}
func submitTransaction(signedTx *chain.SignedTransaction) error {
// send to RPC
}
```
The network validates that the signature matches the signer address in the header. This cryptographic proof is what makes OAP permissionless while still maintaining identity verification.
### DDEX Message Types
#### ERN
[DDEX ERN]() (Electronic Release Notification) is the primary message type in DDEX. It contains all the details around a release across four top-level entities:
1. **Party**: Artists, collaborators, sound engineers, writers, and other contributors to the release.
2. **Resource**: Media files, album art, bitrate, file format, and other low-level artifacts. Contains the CID that is uploaded through the storage protocol.
3. **Release**: The composition of parties, resources, and deals. This is what surfaces as an album, EP, or single on apps.
4. **Deal**: Usage rights, availability windows, and how users can access the release.
ERN messages are stateless, meaning entities can be duplicated across records. Apps and indexers reconcile duplicates by ID to preserve the original DDEX intent.
**Protobuf structure from [ddex-proto](https://github.com/OpenAudio/ddex-proto/blob/main/proto/ddex/ern/v43/v43.proto)**:
```protobuf
message NewReleaseMessage {
MessageHeader message_header = 1;
repeated ReleaseAdmin release_admin = 2;
PartyList party_list = 3;
ResourceList resource_list = 5;
ReleaseList release_list = 7;
DealList deal_list = 8;
}
message Party {
string party_reference = 1;
repeated DetailedPartyId party_id = 5;
repeated PartyNameWithTerritory party_name = 6;
}
message ResourceList {
repeated SoundRecording sound_recording = 1;
repeated Video video = 2;
repeated Image image = 3;
}
message Release {
string release_reference = 1;
repeated ReleaseTypeForReleaseNotification release_type = 2;
ReleaseId release_id = 3;
repeated DisplayTitle display_title = 5;
}
message Deal {
repeated string deal_reference = 1;
DealTerms deal_terms = 3;
}
```
#### Resources
Resources represent the individual assets that make up a release. A `SoundRecording` is an audio track with metadata like duration, ISRC codes, and contributor information. Resources can also include videos, images (album art), text (liner notes), and sheet music. Each resource gets a unique reference ID that releases use to compose their tracklists.
#### Releases
A release ties everything together. It references the parties involved, lists the resources (tracks) in order, and includes metadata like release date, label, and catalog number. The same set of sound recordings might appear in multiple releases. For example, "American Idiot" appears both as a single and as track 1 on the full album.
#### Deals
Deals define how a release can be distributed and consumed. They specify territories, usage types (streaming, download, physical), price tiers, and availability windows. A single release might have multiple deals for different regions or platforms.
#### MEAD
[DDEX MEAD]() (Media Enrichment and Description) extends release metadata with additional descriptive information. While ERNs establish the core structure of a release, MEADs allow ongoing enrichment with things like mood classifications, extended genre tags, promotional text, and other metadata that enhances discoverability.
OAP's implementation leverages the protocol's decentralized nature. Any party can submit MEAD messages to enrich metadata for any release on the network. This creates a collaborative tagging system where fans, curators, and distributors can all contribute. The chain stores all submitted MEADs, but indexers decide which to trust and display.
This trust model enables flexible curation strategies. An indexer might only surface MEADs from verified artists, established music databases, or curated contributor lists. Or they could implement reputation systems and community voting to surface the most valuable enrichment data.
Like ERNs, MEADs are stateless. Multiple submissions for the same release create separate records rather than updates, preserving the full enrichment history.
**Example from [ddex-proto](https://github.com/OpenAudio/ddex-proto/blob/main/proto/ddex/mead/v11/v11.proto)**:
```protobuf
message MeadMessage {
MessageHeader message_header = 1;
MetadataSourceList metadata_source_list = 3;
ResourceInformationList resource_information_list = 5;
ReleaseInformationList release_information_list = 6;
}
message ResourceInformation {
repeated Mood mood = 14;
repeated GenreCategory genre_category = 2;
repeated Lyrics lyrics = 22;
repeated Theme theme = 18;
}
```
#### PIE
[DDEX PIE]() (Party Identification and Enrichment) augments party profiles with biographical data, social media links, and cross-platform identifiers. PIE messages can include social handles, biographical information, additional identifiers from music databases, and other party-specific metadata. This creates a distributed identity system where parties are enriched with information from multiple sources over time.
Like MEADs, any address can submit PIE messages about any party. This enables a crowdsourced approach where fans, industry professionals, and automated systems all contribute identifying information. The same trust considerations apply: indexers must decide which PIE submissions to consider authoritative.
The decentralized PIE system addresses a common music metadata problem: the lack of canonical party identification across different platforms and databases. By allowing multiple identification schemes and enrichment sources, OAP creates a more complete picture of music industry participants while maintaining flexibility for consumers to apply their own trust and verification standards.
PIEs support cross-referencing parties across blockchain addresses, social media accounts, and traditional music industry identifiers, creating a more connected and discoverable ecosystem.
**Example from [ddex-proto](https://github.com/OpenAudio/ddex-proto/blob/main/proto/ddex/pie/v10/v10.proto)**:
```protobuf
message PieMessage {
MessageHeader message_header = 1;
MetadataSourceList metadata_source_list = 2;
PartyList party_list = 3;
}
message Party {
string party_reference = 1;
repeated DetailedPartyIdForParty party_id = 2;
repeated PartyName party_name = 3;
repeated Biography biography = 18;
SocialMediaURL social_media_u_r_l = 20;
}
```
### Go Example
Here's how to use the generated Go libraries to create messages for Durante's "[Enter](https://audius.co/ImDurante/album/enter)":
#### ERN Message
ERN messages define entities separately and link them together with references. In this example, we create four sound recordings from the 13-track album that get packaged into two different releases: a single containing just one track, and the full album containing all four tracks shown here. We also include an image resource for the album art.
Each entity gets a reference ID (like `party-550e8400...` or `track-550e8400...`) that other entities use to link to it. These references are scoped to the message itself. The `ResourceGroup` on each release specifies which tracks to include and in what order by referencing the sound recordings.
The `ProprietaryId` with namespace "OpenAudio" is where blockchain addresses come in. This links Durante's party to an on-chain address, enabling decentralized ownership verification and message signing. Traditional DDEX uses industry identifiers like ISNI as well. While anyone can claim to be Durante with any address, indexers and views verify message signatures on OAP to identify false claims and determine which parties are legitimate.
```go
package main
"github.com/OpenAudio/ddex-proto/gen/ddex/ern/v43"
)
func main() {
// Define the artist
durante := &v43.Party{
PartyReference: "party-550e8400-e29b-41d4-a716-446655440001",
PartyName: []*v43.PartyNameWithTerritory{{
FullName: &v43.Name{Value: "Durante"},
}},
PartyId: []*v43.DetailedPartyId{{
ProprietaryId: []*v43.ProprietaryId{{
Namespace: "OpenAudio",
Value: "0x742d35cc6634C0532925a3b8D2c1c2F78E2c7aB2",
}},
}},
}
// Define album art
albumArt := &v43.Image{
ResourceReference: "image-550e8400-e29b-41d4-a716-446655440020",
ImageType: "FrontCoverImage",
ImageCodecType: &v43.ImageCodecType{Value: "JPEG"},
ImageHeight: 3000,
ImageWidth: 3000,
}
// Define sound recordings for the album
lmk := &v43.SoundRecording{
ResourceReference: "track-550e8400-e29b-41d4-a716-446655440010",
DisplayTitleText: []*v43.DisplayTitleText{{
Value: "LMK feat. CRi",
}},
Duration: &v43.Duration{Value: "PT3M53S"},
}
holdingOn := &v43.SoundRecording{
ResourceReference: "track-550e8400-e29b-41d4-a716-446655440011",
DisplayTitleText: []*v43.DisplayTitleText{{
Value: "Holding On feat. Nathan Nicholson",
}},
Duration: &v43.Duration{Value: "PT3M58S"},
}
opalescent := &v43.SoundRecording{
ResourceReference: "track-550e8400-e29b-41d4-a716-446655440012",
DisplayTitleText: []*v43.DisplayTitleText{{
Value: "Opalescent",
}},
Duration: &v43.Duration{Value: "PT4M8S"},
}
remedy := &v43.SoundRecording{
ResourceReference: "track-550e8400-e29b-41d4-a716-446655440013",
DisplayTitleText: []*v43.DisplayTitleText{{
Value: "Remedy feat. Running Touch",
}},
Duration: &v43.Duration{Value: "PT3M45S"},
}
// Single release - references only one track
single := &v43.Release{
ReleaseReference: "release-550e8400-e29b-41d4-a716-446655440100",
ReleaseId: &v43.ReleaseId{
Icpn: "5060236639691",
},
DisplayTitleText: []*v43.DisplayTitleText{{
Value: "LMK",
}},
ReleaseDate: "2024-04-05",
ResourceGroup: &v43.ResourceGroup{
ResourceGroupContentItem: []*v43.ResourceGroupContentItem{{
SequenceNumber: 1,
ReleaseResourceReference: "track-550e8400-e29b-41d4-a716-446655440010",
}},
},
}
// Album release - references all tracks and album art
album := &v43.Release{
ReleaseReference: "release-550e8400-e29b-41d4-a716-446655440101",
ReleaseId: &v43.ReleaseId{
Icpn: "5060236639691",
},
DisplayTitleText: []*v43.DisplayTitleText{{
Value: "Enter",
}},
ReleaseDate: "2024-04-05",
PLine: &v43.PLineWithDefault{{
Year: "2024",
PLineText: "Anjunadeep",
}},
ResourceGroup: &v43.ResourceGroup{
ResourceGroupContentItem: []*v43.ResourceGroupContentItem{
{
SequenceNumber: 1,
ReleaseResourceReference: "track-550e8400-e29b-41d4-a716-446655440010",
},
{
SequenceNumber: 2,
ReleaseResourceReference: "track-550e8400-e29b-41d4-a716-446655440011",
},
{
SequenceNumber: 3,
ReleaseResourceReference: "track-550e8400-e29b-41d4-a716-446655440012",
},
{
SequenceNumber: 4,
ReleaseResourceReference: "track-550e8400-e29b-41d4-a716-446655440013",
},
},
},
ReleaseResourceReferenceList: &v43.ReleaseResourceReferenceList{
ReleaseResourceReference: []string{"image-550e8400-e29b-41d4-a716-446655440020"},
},
}
// Compose the ERN message
ern := &v43.NewReleaseMessage{
PartyList: &v43.PartyList{
Party: []*v43.Party{durante},
},
ResourceList: &v43.ResourceList{
SoundRecording: []*v43.SoundRecording{
lmk,
holdingOn,
opalescent,
remedy,
},
Image: []*v43.Image{albumArt},
},
ReleaseList: &v43.ReleaseList{
Release: []*v43.Release{single, album},
},
}
}
```
#### MEAD Message
Add enrichment metadata to the title track. Anyone can submit this to add mood, genre, and thematic tags:
```go
"github.com/OpenAudio/ddex-proto/gen/ddex/mead/v11"
)
func enrichTrack() {
mead := &v11.MeadMessage{
ResourceInformationList: &v11.ResourceInformationList{
ResourceInformation: []*v11.ResourceInformation{{
ResourceSummary: &v11.ResourceSummary{
ResourceId: &v11.ResourceIdWithoutFlag{
ProprietaryId: []*v11.ProprietaryId{{
Namespace: "OpenAudio",
Value: "track-550e8400-e29b-41d4-a716-446655440010",
}},
},
},
Mood: []*v11.Mood{{
Value: &v11.MoodValue{Value: "Ethereal"},
}},
GenreCategory: []*v11.GenreCategory{{
Value: &v11.GenreCategoryValue{Value: "Progressive House"},
}},
Theme: []*v11.Theme{{
Value: &v11.ThemeValue{Value: "Atmospheric"},
}},
}},
},
}
}
```
#### PIE Message
Enrich Durante's party profile with biographical information and social links:
```go
"github.com/OpenAudio/ddex-proto/gen/ddex/pie/v10"
)
func enrichParty() {
pie := &v10.PieMessage{
PartyList: &v10.PartyList{
Party: []*v10.Party{{
PartyReference: "party-550e8400-e29b-41d4-a716-446655440001",
PartyName: []*v10.PartyName{{
FullName: &v10.NameWithScriptCode{
Name: &v10.Name{Value: "Durante"},
},
}},
Biography: []*v10.Biography{{
Text: []*v10.BiographyText{{
Value: "Durante is an American electronic music producer and DJ, known for his melodic progressive house sound. After 18 years of musical journey, he released his debut album 'Enter' on Anjunadeep in 2024.",
}},
}},
SocialMediaUrl: &v10.SocialMediaURL{
Value: "https://www.instagram.com/imdurante",
},
}},
},
}
}
```
---
## Entity Manager
Entity Manager is a simpler wire format for Create/Update operations on entities like Tracks, Users, and Playlists. It originates from Audius and uses EIP-712 typed signing. Each message specifies an entity type, ID, action, and JSON metadata. The signer's address is recovered from the signature and written into the `signer` field before submission.
### Message Structure
```protobuf
message ManageEntityLegacy {
int64 user_id = 1;
string entity_type = 2;
int64 entity_id = 3;
string action = 4;
string metadata = 5;
string signature = 6;
string signer = 7;
string nonce = 8;
}
```
`SignedTransaction` wraps it in a oneof alongside other v1 transaction types:
```protobuf
message SignedTransaction {
string signature = 1;
string request_id = 2;
oneof transaction {
ManageEntityLegacy manage_entity = 1003;
// ... other types
}
}
```
### Track Metadata
For `entity_type: "Track"` with `action: "Create"` or `"Update"`, the `metadata` JSON can include:
- **Basic**: `title`, `genre`, `release_date`, `cid` (or `data.track_cid`) — the transcoded audio CID
- **Programmable distribution**: `access_authorities` — list of wallet addresses that can sign to authorize stream access. If omitted, the signer is used.
### Signing and Sending
Signing uses EIP-712 with the Entity Manager contract domain. The go-openaudio `server.SignManageEntity` helper fills `signer` and `signature`:
```go
"context"
"encoding/json"
"fmt"
"time"
"connectrpc.com/connect"
corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1"
"github.com/OpenAudio/go-openaudio/pkg/core/config"
"github.com/OpenAudio/go-openaudio/pkg/core/server"
"github.com/OpenAudio/go-openaudio/pkg/sdk"
"github.com/google/uuid"
)
func createTrack(ctx context.Context, auds *sdk.OpenAudioSDK, transcodedCID string) error {
entityID := time.Now().UnixNano() % 1000000
if entityID < 0 {
entityID = -entityID
}
metadata := map[string]interface{}{
"title": "My Track",
"genre": "Electronic",
"release_date": "2025-01-15",
"cid": transcodedCID,
}
metadataJSON, _ := json.Marshal(metadata)
manageEntity := &corev1.ManageEntityLegacy{
UserId: 1,
EntityType: "Track",
EntityId: entityID,
Action: "Create",
Metadata: string(metadataJSON),
Nonce: fmt.Sprintf("0x%064x", entityID),
Signer: "",
}
cfg := &config.Config{
AcdcEntityManagerAddress: config.DevAcdcAddress,
AcdcChainID: config.DevAcdcChainID,
}
if err := server.SignManageEntity(cfg, manageEntity, auds.PrivKey()); err != nil {
return err
}
stx := &corev1.SignedTransaction{
RequestId: uuid.NewString(),
Transaction: &corev1.SignedTransaction_ManageEntity{
ManageEntity: manageEntity,
},
}
_, err := auds.Core.SendTransaction(ctx, connect.NewRequest(&corev1.SendTransactionRequest{
Transaction: stx,
}))
return err
}
```
For programmable distribution, include `access_authorities` in the metadata so only listed wallets can sign stream URLs. See `examples/upload` and `examples/programmable-distribution` in [go-openaudio](https://github.com/OpenAudio/go-openaudio) for full flows, or `apps/packages/sdk` in the Audius monorepo for TypeScript/JS usage.
---
> Source: https://docs.audius.co/concepts/media-storage

# Media Storage
Media storage on the Open Audio Protocol elastically scales with usage.
The underlying architecture, referred to Mediorum in code, is designed to be a flexible, fast, and secure.
Every node is responsible for storing a share of the network, calculated from the total storage of all media files on the network, the replication factor, and the total number of validator nodes serving traffic.
The total storage required for a set of media files per node can be calculated as:
$$
\text{storageCommitment} = \frac{S * R}{N}
$$
where:
- $S$ = total storage required for all media files
- $R$ = replication factor (number of copies each file is stored across the network)
- $N$ = number of nodes in the network
## How files are stored
Mediorum uses [Rendezvous Hashing](https://en.wikipedia.org/wiki/Rendezvous_hashing) based on sha256 hashes of hostnames and content addressable identifiers to place content in a deterministic way across the network.
[Repair] and [Reaper] processes monitor and resync catalog as the node list changes (new validators get registered, existing validators unstake, or replication factors change).
## Storage proofs
As file storage is deterministic, all nodes periodically attest to other's ability to serve content they are required to. These are known as storage proofs, and being unable to meet quota can lead to slashing.
---
> Source: https://docs.audius.co/concepts/moderation

# Moderation
Validator nodes specify a moderation party to adhere to guidance from. Moderation parties exist to meet [DMCA](https://en.wikipedia.org/wiki/Digital_Millennium_Copyright_Act) and other content moderation needs. When content is uploaded to the protocol, moderation parties may freely access content, provided that they are specified by the hosting validator nodes.
Tiki Labs, Inc. offers a free US-based, closed source fingerprinting library that makes use of modern tooling used across the music industry. Tiki Labs, Inc.'s moderation party identifier (a `secp256k1` address) is set as the default in the go-openaudio implementation.
| id | address | endpoint | owner |
| :- | :------------------------------------------- | :------------------------------------------------------- | :-------------- |
| 1 | `0x071718c690fEfCE978bb39366709f5E51A82A3CD` | [https://notifier.audius.co](https://notifier.audius.co) | Tiki Labs, Inc. |
The smart contract address that lists available moderation parties can be viewed on [Etherscan](https://etherscan.io/address/0x6f08105c8CEef2BC5653640fcdbBE1e7bb519D39).
---
> Source: https://docs.audius.co/concepts/indexers-views

# Indexers & Views
Coming soon.
In the meantime, refer to the [Audius API implementation](https://github.com/AudiusProject/api) or [audiusd ETL](https://github.com/AudiusProject/audiusd/tree/main/pkg/etl) package.
---
> Source: https://docs.audius.co/concepts/artist-coins

# Artist Coins
Artist Coins are the fan club experience of Audius + the Open Audio Protocol. Through launching an Artist Coin, an artist is able to own the economy of their brand, earn revenue from attention, and organize and reward their fanbase. They are the fundamental piece of the Music Capital Market.
## How coins work

The graphic above demonstrates the relationship between artists, fans, and $AUDIO.
The purpose of the Artist Coins is to serve as the web3 fanclub for an artist. The [Super Fan](https://www.musicweek.com/labels/read/umg-investor-presentation-reveals-major-s-plans-for-superfans-and-streaming-growth/090486) is a well understood concept in the music industry, but common streaming platforms of today handicap the ability for an artist to tap into that economy. Artist Coins are therefore havily paired with utility out of the box: releasing music directly to coin holders (members), creating custom reward pools for ongoing campaigns, and providing a provable framework for applications to build social hubs on top of coin fundamentals.
All Artist Coins are powered by Solana and Meteora's liquidity infrastructure. When an artist launches a coin, it is automatically created with a [Bonding Curve](https://www.coinbase.com/learn/advanced-trading/what-is-a-bonding-curve) quoted against \$AUDIO. This allows for instant trading, and also ensures a fair launch. Given the liqudity of $AUDIO in the Ethereum and Solana ecosystems, this immediately unlocks trading across many venues. Coins graduate to an [Automated Market Maker](https://www.coinbase.com/learn/advanced-trading/what-is-an-automated-market-maker-amm) when they reach a target market cap, creating a more open trading environment.
Developer applications, like the [Audius launchpad](https://audius.co/coins) provide frontend experiences to launch coins, but as all the underlying tooling and infrastructure is open, any app can provide access to Artist Coins.
## Tokenomics
All Artist Coins follow the same framework so users know what to expect:
* Bonding curve paired against $AUDIO
* 1,000,000,000 total supply
* Initial market cap of 100,000 $AUDIO
* Graduation market cap of 1,000,000 $AUDIO
* 50% of supply allocated to the artist, unlocking linearly over 5 years (starting from graduation):
* Artists can buy at launch, holding that back for themselves or allocating to fan reward pools
* Artists can shift a portion of allocation to fan reward pools
* 25% of a coin's supply is sold through the curve to reach graduation
* 20% of a coin's supply is moved to permanently locked liquidity in the AMM post-graduation
* 5% of a coin's supply is moved to a rewards pool post-graduation
* 1% trading fee on all coins earned in $AUDIO
* 50% of fees artists
* 50% of fees Audius community treasury
## Maths
The bonding curve config deployment shows the application of the above tokenomics.\
[solscan link](https://solscan.io/tx/2FgMXPYkH8rKdc1VUmX7hUUxsai4EA3oWaYZ7AJnYr3Q4Zf8mnd1sVQhLXLGpSJWD3BgPNmjG47jiZW2aji6xp9J)
Artist Coins follow a concave shape where liquidity

The math used to derive the bonding curve shape can be run via ipynb:
https://gist.github.com/raymondjacobson/08296dd65cd9fff37a889b947af7ad85
The exact sqrtPrice,Liquidity values used for the bonding curve design are:
```json
[
{
"sqrt_price": "49454822942198928",
"liquidity": "316151256542714767379182355742720"
},
{
"sqrt_price": "67463150367941992",
"liquidity": "1034142099640443506675420695101440"
},
{
"sqrt_price": "81589054430670160",
"liquidity": "1993482779222476007980168292335616"
},
{
"sqrt_price": "93607002666542880",
"liquidity": "3426004632472395272977352402927616"
},
{
"sqrt_price": "104248587969034256",
"liquidity": "5527075263389051486360336718626816"
},
{
"sqrt_price": "113900242488156192",
"liquidity": "8548850210158313829039805609017344"
},
{
"sqrt_price": "122795612235063552",
"liquidity": "12820672237426984956983602256543744"
},
{
"sqrt_price": "131088746769154048",
"liquidity": "18771222513185901677116104399388672"
},
{
"sqrt_price": "138887568467558320",
"liquidity": "26955439896665360498417791527813120"
},
{
"sqrt_price": "146271165374977472",
"liquidity": "38087728759187777947138400779763712"
},
{
"sqrt_price": "153299546527304896",
"liquidity": "53082960253944572826836697362726912"
},
{
"sqrt_price": "160019524155024992",
"liquidity": "73107000099851162532571430724304896"
},
{
"sqrt_price": "166468451233631424",
"liquidity": "99638821061952779942546313536602112"
},
{
"sqrt_price": "172676699069235264",
"liquidity": "134546659677401790102417975826972672"
},
{
"sqrt_price": "178669358164097472",
"liquidity": "180181158020974273473618784576077824"
},
{
"sqrt_price": "184467440737095520",
"liquidity": "239489003397306076375008380274606080"
}
]
```
---
> Source: https://docs.audius.co/tutorials/run-a-node

# Run A Node
This guide shows you how to run a validator node or an RPC node.
> **Info:** Validators are registered on chain, earn staking rewards, and are required to commit file storage to the network. RPC nodes make data available for off-chain products and do not need to commit file storage.
## Specs
> **Note:** go-openaudio
> [code](https://github.com/openaudio/go-openaudio) [releases](https://hub.docker.com/r/openaudio/go-openaudio/tags)
go-openaudio is a single docker container that can be run on most hardware with a very minimal set up.
> Stable releases are published monthly at [`openaudio/go-openaudio:stable`](https://hub.docker.com/r/openaudio/go-openaudio).\
> Nightly (edge) builds are tagged by commit hash.\
> $AUDIO governance votes on adoption of versions. That version can be found [here](https://staking.openaudio.org/#/nodes).
It is recommended to provision a VM with at least:
- 16GB of memory
- 8 cpu cores
- 200GB boot disk
- Datacenter-grade network access
Additionally, validator nodes require file storage. You may elect to use a disk, or any S3-compatible (elastic) blob storage. Generally blob-storage is substantially more cost-effective.
> **Tip:** The amount of storage (bytes) you will need to provide can be found via the RPC [storage.v1.StorageService/GetStatus](https://creatornode.audius.co/storage.v1.StorageService/GetStatus). This number is calculated from the total storage of the network and the number of nodes. See [media-storage](/concepts/media-storage) for more information.
## Set up
These instructions will show you a minimal `docker compose` set up with an auto-upgrade policy. You may choose your own path instead!
**1. Provision a keypair**
This is the Ethereum secp256k1 keypair for your node. This key never holds funds, but is used to sign requests.
```bash
pip install eth-keys
python -c "from eth_keys import keys;import os;p=keys.PrivateKey(os.urandom(32));print('OPENAUDIO_DELEGATE_WALLET=',p.public_key.to_checksum_address(),'\nOPENAUDIO_DELEGATE_PRIVATE_KEY=',p.to_hex(),sep='')"
```
**2. Create an .env file**
> **Tip:** The variables below are the minimum required to boot a node. For the complete list of `OPENAUDIO_*` variables the binary recognises, see [Node Environment Variables](/reference/node-environment-variables).
```bash // [.env]
OPENAUDIO_NODE_ENDPOINT=https://my-node.com
# The keypair you generated above
OPENAUDIO_DELEGATE_WALLET=0x01234567890abcdef01234567890abcdef012345
OPENAUDIO_DELEGATE_PRIVATE_KEY=01234567890abcdef01234567890abcdef01234567890abcdef01234567890ab
# Your wallet address that is staking. If running an RPC, set to the same value as `OPENAUDIO_DELEGATE_WALLET`
OPENAUDIO_OWNER_WALLET=0x01234567890abcdef01234567890abcdef012345
# Validator storage configuration
# If electing to use blob storage, set:
OPENAUDIO_STORAGE_DRIVER_URL=s3://my-s3-bucket
AWS_REGION=us-west-2
AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# If electing to use disk storage, to store blobs at `/data/storage`, set:
# OPENAUDIO_STORAGE_DRIVER_URL=file:///data/storage?no_tmp_dir=true
```
**3. Create a docker compose file**
```yaml // [docker-compose.yml]
services:
my-node:
image: openaudio/go-openaudio:stable
container_name: my-node
env_file:
- .env
volumes:
- /root/openaudio-prod-data:/data
ports:
- 80:80
- 443:443
- 26656:26656
watchtower:
image: containrrr/watchtower
container_name: watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --cleanup --interval 3600 my-node
```
**4. Run your node**
```
docker compose up -d
```
**5. Monitor your node**
```bash
curl my-node.com/health-check
```
or visit [https://my-node.com/console](https://my-node.com/console) in your web browser
> **Tip:** Your node will begin syncing both [core](/concepts/validators#core) data as well as being hosting files using [mediorum](/concepts/validators#mediorum). This process can take a few hours. When it is finished, your node will no longer say syncing.
>
> The /health-check endpoint also provides `live` and `ready` booleans that can be used to monitor ongoing status.
## Registration
Visit [staking.openaudio.org](https://staking.openaudio.org) to register your node.
1. Connect a wallet or multisig using wallet connect button at the top of the page
2. Click `Nodes` in the navigation menu
3. Click `Register New Node`
4. Fill out `NODE ENDPOINT`, `NODE WALLET ADDRESS` (delegate address) based on your configuration above
5. Set a desired stake amount (you must have this much $AUDIO already in your wallet)
6. Click register and then sign & send the two transactions
> **Note:** Registration requires a minimum bond of 200,000 $AUDIO.
Alternatively you can use [etherscan directly](https://etherscan.io/address/0xD17A9bc90c582249e211a4f4b16721e7f65156c8#writeProxyContract) using the `register` function or the [Audius Protocol Dashboard](https://dashboard.audius.org).
## Blob Storage Options
Blob storage is configured using the popular [go-cdk](https://gocloud.dev/) library and should work with most blob storage offerings on the market. We have provided some example config below to get started with some commonly used providers.
```bash
# AWS S3:
OPENAUDIO_STORAGE_DRIVER_URL=s3://my-s3-bucket
AWS_REGION=us-west-2
AWS_ACCESS_KEY_ID=XXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXX
# GCP Cloud Storage
# you will need an additional mount for the credentials
# i.e. `-v google-application-credentials.json:/tmp/google-application-credentials.json`
OPENAUDIO_STORAGE_DRIVER_URL=gs://my-gcs-bucket
GOOGLE_APPLICATION_CREDENTIALS=/tmp/google-application-credentials.json
# Cloudflare R2:
OPENAUDIO_STORAGE_DRIVER_URL='s3://my-r2-bucket?endpoint=https://abc123def456.r2.cloudflarestorage.com'
AWS_REGION=wnam
AWS_ACCESS_KEY_ID=XXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXX
# Vultr:
OPENAUDIO_STORAGE_DRIVER_URL='s3://my-vultr-bucket?endpoint=https://sjc1.vultrobjects.com'
AWS_REGION=sjc1
AWS_ACCESS_KEY_ID=XXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXX
# Digital Ocean Spaces:
# Note: please delete your bucket name from the beginning of the https endpoint URL
OPENAUDIO_STORAGE_DRIVER_URL='s3://my-spaces-bucket?endpoint=https://sfo3.digitaloceanspaces.com'
AWS_REGION=sfo3
AWS_ACCESS_KEY_ID=XXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXX
# Backblaze B2:
OPENAUDIO_STORAGE_DRIVER_URL='s3://my-b2-bucket?endpoint=https://s3.us-west-004.backblazeb2.com'
AWS_REGION=us-west-004
AWS_ACCESS_KEY_ID=XXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXX
```
### Moving to blob storage
To move from filesystem/disk storage to blob Storage
1. Set the approprivate environment variables based on the above configuration for your provider.
2. Set the env var
```
OPENAUDIO_STORAGE_DRIVER_URL_MOVE_FROM="file:///tmp/mediorum/blobs?no_tmp_dir=true"
```
3. Restart your node.
You will see this log line:
```
info Moving all files from file:///tmp/mediorum/blobs?no_tmp_dir=true to s3://my-s3-bucket. This may take a few hours...
{"node": "https://my-node.com", "service": "mediorum"}
```
When finished, you will this log line:
```
info Finished moving files between buckets. Please remove OPENAUDIO_STORAGE_DRIVER_URL_MOVE_FROM from your environment and restart the server. {"node": "https://my-node.com", "service": "mediorum"}
```
> **Note:** This change will take a few hours.
4. When it completes, or when the target blob storage resembles the expected size, remove `OPENAUDIO_STORAGE_DRIVER_URL_MOVE_FROM` from your env.
5. Restart your node. Any missed files will be re-synced by the mediorum repair job.
## Node Type and State Sync
By default, nodes run in a full configuration that prunes old blocks (≈1 week retained) and stores only this node's replication slice of blobs. That keeps disk usage bounded and is the right choice for most operators.
The following env vars opt your node into other configurations. See the [Environment Variables](/reference/node-environment-variables) reference for the full list.
**`OPENAUDIO_STORE_ALL`**
default: `false`
Store every blob the network has, not just this node's replication slice. Used
for backup / mirror nodes and is required to make use of
`OPENAUDIO_ARCHIVE_STORAGE_DRIVER_URL`. Disk usage grows with the total network
footprint.
**`OPENAUDIO_ARCHIVE`**
default: `false`
Run in archival mode: never prune blocks. Required if you want to serve
historical chain queries below the default retain window. Disk usage grows
linearly with chain height.
**`OPENAUDIO_STATE_SYNC_SERVE_SNAPSHOTS`**
default: `false`
Periodically produce CometBFT state-sync snapshots and serve them to
bootstrapping peers. Turning this on helps the network — new nodes can sync from
your snapshots instead of replaying from genesis — at the cost of extra disk for
the retained snapshots (controlled by `OPENAUDIO_STATE_SYNC_KEEP`).
---
> Source: https://docs.audius.co/tutorials/upload-to-the-protocol

# Upload to the Protocol
This guide shows how to upload audio files to the Open Audio Protocol using Go. You'll use two methods: the regular HTTP upload and the resumable TUS upload. Both produce a transcoded CID you can use when creating tracks or releases.
## Prerequisites
- Go 1.21+
- A node endpoint (e.g. devnet `https://node1.oap.devnet` or production `https://creatornode.audius.co`)
- An Ethereum secp256k1 private key to sign uploads
---
## Regular upload (POST /uploads)
The regular upload sends the file in one request. You compute the file's CID, sign it to prove ownership, then POST to the node's `/uploads` endpoint. The node transcodes the audio (e.g. to 320kbps MP3) and stores it in Mediorum.
```go
package main
"context"
"log"
"os"
corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1"
"github.com/OpenAudio/go-openaudio/pkg/common"
"github.com/OpenAudio/go-openaudio/pkg/hashes"
"github.com/OpenAudio/go-openaudio/pkg/sdk"
"github.com/OpenAudio/go-openaudio/pkg/sdk/mediorum"
"google.golang.org/protobuf/proto"
)
func main() {
ctx := context.Background()
serverAddr := "https://node1.oap.devnet"
privKeyPath := "path/to/your/private_key.txt"
audioPath := "my-track.mp3"
auds := sdk.NewOpenAudioSDK(serverAddr)
if err := auds.Init(ctx); err != nil {
log.Fatalf("failed to init SDK: %v", err)
}
if err := auds.ReadPrivKey(privKeyPath); err != nil {
log.Fatalf("failed to read private key: %v", err)
}
audioFile, err := os.Open(audioPath)
if err != nil {
log.Fatalf("failed to open audio file: %v", err)
}
defer audioFile.Close()
// 1. Compute file CID (content-addressable identifier)
fileCID, err := hashes.ComputeFileCID(audioFile)
if err != nil {
log.Fatalf("failed to compute file CID: %v", err)
}
audioFile.Seek(0, 0)
// 2. Sign the CID to authorize the upload
uploadSigData := &corev1.UploadSignature{Cid: fileCID}
uploadSigBytes, err := proto.Marshal(uploadSigData)
if err != nil {
log.Fatalf("failed to marshal upload signature: %v", err)
}
uploadSignature, err := common.EthSign(auds.PrivKey(), uploadSigBytes)
if err != nil {
log.Fatalf("failed to sign upload: %v", err)
}
// 3. Upload with signature; wait for transcode
uploadOpts := &mediorum.UploadOptions{
Template: "audio",
Signature: uploadSignature,
WaitForTranscode: true,
WaitForFileUpload: false,
OriginalCID: fileCID,
}
uploads, err := auds.Mediorum.UploadFile(ctx, audioFile, "my-track.mp3", uploadOpts)
if err != nil {
log.Fatalf("failed to upload file: %v", err)
}
if len(uploads) == 0 {
log.Fatal("no uploads returned")
}
if uploads[0].Status != "done" {
log.Fatalf("upload failed: %s", uploads[0].Error)
}
transcodedCID := uploads[0].GetTranscodedCID()
log.Printf("uploaded transcoded CID: %s", transcodedCID)
}
```
---
## Resumable upload (TUS)
The TUS protocol supports resumable uploads: if the connection drops, the client can resume from the last byte. This is better for large files or unstable networks.
```go
package main
"context"
"io"
"log"
"os"
"connectrpc.com/connect"
v1storage "github.com/OpenAudio/go-openaudio/pkg/api/storage/v1"
"github.com/OpenAudio/go-openaudio/pkg/sdk"
)
func main() {
ctx := context.Background()
serverAddr := "https://node1.oap.devnet"
privKeyPath := "path/to/your/private_key.txt"
audioPath := "my-track.mp3"
auds := sdk.NewOpenAudioSDK(serverAddr)
if err := auds.Init(ctx); err != nil {
log.Fatalf("failed to init SDK: %v", err)
}
if err := auds.ReadPrivKey(privKeyPath); err != nil {
log.Fatalf("failed to read private key: %v", err)
}
audioFile, err := os.Open(audioPath)
if err != nil {
log.Fatalf("failed to open file: %v", err)
}
defer audioFile.Close()
audioData, err := io.ReadAll(audioFile)
if err != nil {
log.Fatalf("failed to read file: %v", err)
}
res, err := auds.Storage.UploadFilesTus(ctx, connect.NewRequest(&v1storage.UploadFilesRequest{
UserWallet: auds.Address(),
Template: "audio",
Files: []*v1storage.File{
{
Filename: "my-track.mp3",
Data: audioData,
},
},
}))
if err != nil {
log.Fatalf("failed to upload: %v", err)
}
if len(res.Msg.Uploads) == 0 {
log.Fatalf("no upload returned")
}
upload := res.Msg.Uploads[0]
// Use transcoded CID if available, otherwise original
cid := upload.OrigFileCid
if tc, ok := upload.TranscodeResults["320"]; ok && tc != "" {
cid = tc
}
log.Printf("uploaded CID: %s", cid)
}
```
`UploadFilesTus` streams the file via TUS, polls until transcoding finishes, and returns the upload with `OrigFileCid` and `TranscodeResults`. For audio, the `320` key holds the transcoded MP3 CID.
---
## Next steps
Once you have a transcoded CID, you can use it when creating tracks or releases via the Entity Manager or DDEX wire protocol. See [Gate release access](/tutorials/gate-release-access) for gated distribution, or the [Wire Protocol](/concepts/wire-protocol) for release formats.
---
> Source: https://docs.audius.co/tutorials/gate-release-access

# Gate Release Access
This guide will walk you through how to release a track on the Open Audio Protocol that has gated release access—where only requests you authorize can stream the audio. You'll build an "access server" that signs stream URLs on behalf of your users, giving you full control over who can access the content.
## How It Works
With **programmable distribution**, you upload a track and specify one or more wallet addresses as `access_authorities`. Only those addresses can sign requests to stream the track. The node rejects unsigned or wrongly signed requests with 401.
Your **access server** holds the signing key. When a user hits your server (e.g. `/stream`), you verify they're allowed (e.g. logged in, paid, follows you) and, if so, sign a short-lived stream URL and redirect them to the node. The node validates your signature and serves the audio. Without your server, direct requests to the node fail.
## Prerequisites
- Go 1.21+
- Access to a production validator node (e.g. `creatornode.audius.co`)
---
## Step 1: Upload the audio
Upload your audio file to Mediorum and get back a transcoded CID. The node transcodes to the formats it will serve. For the full upload flow (including resumable TUS), see [Upload to the protocol](/tutorials/upload-to-the-protocol).
```go
audioFile, err := os.Open("my-track.mp3")
if err != nil {
log.Fatalf("failed to open audio file: %v", err)
}
defer audioFile.Close()
fileCID, err := hashes.ComputeFileCID(audioFile)
if err != nil {
log.Fatalf("failed to compute file CID: %v", err)
}
audioFile.Seek(0, 0)
uploadSigData := &corev1.UploadSignature{Cid: fileCID}
uploadSigBytes, err := proto.Marshal(uploadSigData)
if err != nil {
log.Fatalf("failed to marshal upload signature: %v", err)
}
uploadSignature, err := common.EthSign(auds.PrivKey(), uploadSigBytes)
if err != nil {
log.Fatalf("failed to sign upload: %v", err)
}
uploadOpts := &mediorum.UploadOptions{
Template: "audio",
Signature: uploadSignature,
WaitForTranscode: true,
WaitForFileUpload: false,
OriginalCID: fileCID,
}
uploads, err := auds.Mediorum.UploadFile(ctx, audioFile, "my-track.mp3", uploadOpts)
if err != nil {
log.Fatalf("failed to upload file: %v", err)
}
if len(uploads) == 0 {
log.Fatal("no uploads returned")
}
transcodedCID := uploads[0].GetTranscodedCID()
```
---
## Step 2: Create the track with access_authorities
Create a Track entity via ManageEntity and set `access_authorities` to your signing wallet(s). Only those wallets can authorize stream requests.
```go
signerAddress := auds.Address()
entityID := time.Now().UnixNano() % 1000000
if entityID < 0 {
entityID = -entityID
}
metadata := map[string]interface{}{
"cid": "",
"access_authorities": []string{signerAddress}, // your server's wallet
"data": map[string]interface{}{
"title": "Gated Track",
"genre": "Electronic",
"release_date": time.Now().Format("2006-01-02"),
"track_cid": transcodedCID,
"owner_id": 1,
},
}
metadataJSON, _ := json.Marshal(metadata)
manageEntity := &corev1.ManageEntityLegacy{
UserId: 1,
EntityType: "Track",
EntityId: entityID,
Action: "Create",
Metadata: string(metadataJSON),
Nonce: fmt.Sprintf("0x%064x", entityID),
Signer: "",
}
cfg := &config.Config{
AcdcEntityManagerAddress: config.ProdAcdcAddress,
AcdcChainID: config.ProdAcdcChainID,
}
server.SignManageEntity(cfg, manageEntity, auds.PrivKey())
stx := &corev1.SignedTransaction{
RequestId: uuid.NewString(),
Transaction: &corev1.SignedTransaction_ManageEntity{
ManageEntity: manageEntity,
},
}
auds.Core.SendTransaction(ctx, connect.NewRequest(&corev1.SendTransactionRequest{
Transaction: stx,
}))
```
---
## Step 3: Build the access server
The server signs stream URLs when users are authorized. The signature format is JCS-canonicalized JSON plus an Ethereum personal sign.
```go
type StreamHandler struct {
privateKey *ecdsa.PrivateKey
trackID int64
cid string
nodeBaseURL string
}
func (h *StreamHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
sigData := &signature.SignatureData{
Cid: h.cid,
Timestamp: time.Now().UnixMilli(),
UploadID: "",
ShouldCache: 0,
TrackId: h.trackID,
UserID: 0,
}
sigStr, err := signature.GenerateQueryStringFromSignatureData(sigData, h.privateKey)
if err != nil {
http.Error(w, "failed to sign", http.StatusInternalServerError)
return
}
streamURL := fmt.Sprintf("%s/tracks/stream/%d?signature=%s",
h.nodeBaseURL, h.trackID, url.QueryEscape(sigStr))
http.Redirect(w, r, streamURL, http.StatusFound)
}
```
---
## Full Go example
```go
package main
"context"
"crypto/ecdsa"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"net/url"
"os"
"time"
"connectrpc.com/connect"
corev1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1"
"github.com/OpenAudio/go-openaudio/pkg/common"
"github.com/OpenAudio/go-openaudio/pkg/core/config"
"github.com/OpenAudio/go-openaudio/pkg/core/server"
"github.com/OpenAudio/go-openaudio/pkg/hashes"
"github.com/OpenAudio/go-openaudio/pkg/mediorum/server/signature"
"github.com/OpenAudio/go-openaudio/pkg/sdk"
"github.com/OpenAudio/go-openaudio/pkg/sdk/mediorum"
"github.com/ethereum/go-ethereum/crypto"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
)
func main() {
ctx := context.Background()
validator := flag.String("validator", "creatornode.audius.co", "Validator endpoint")
port := flag.String("port", "8800", "Server port")
flag.Parse()
signerKey, _ := crypto.GenerateKey()
auds := sdk.NewOpenAudioSDK(*validator)
auds.Init(ctx)
auds.SetPrivKey(signerKey)
cid, trackID, err := uploadGatedTrack(ctx, auds)
if err != nil {
log.Fatalf("upload failed: %v", err)
}
nodeBaseURL := fmt.Sprintf("https://%s", *validator)
handler := &StreamHandler{
privateKey: signerKey,
trackID: trackID,
cid: cid,
nodeBaseURL: nodeBaseURL,
}
http.Handle("/stream", handler)
log.Printf("Stream at http://localhost:%s/stream", *port)
http.ListenAndServe(":"+*port, nil)
}
type StreamHandler struct {
privateKey *ecdsa.PrivateKey
trackID int64
cid string
nodeBaseURL string
}
func (h *StreamHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
sigData := &signature.SignatureData{
Cid: h.cid, Timestamp: time.Now().UnixMilli(),
UploadID: "", ShouldCache: 0, TrackId: h.trackID, UserID: 0,
}
sigStr, err := signature.GenerateQueryStringFromSignatureData(sigData, h.privateKey)
if err != nil {
http.Error(w, "failed to sign", http.StatusInternalServerError)
return
}
u := fmt.Sprintf("%s/tracks/stream/%d?signature=%s",
h.nodeBaseURL, h.trackID, url.QueryEscape(sigStr))
http.Redirect(w, r, u, http.StatusFound)
}
func uploadGatedTrack(ctx context.Context, auds *sdk.OpenAudioSDK) (string, int64, error) {
audioFile, _ := os.Open("my-track.mp3")
defer audioFile.Close()
fileCID, _ := hashes.ComputeFileCID(audioFile)
audioFile.Seek(0, 0)
uploadSigData := &corev1.UploadSignature{Cid: fileCID}
uploadSigBytes, _ := proto.Marshal(uploadSigData)
uploadSignature, _ := common.EthSign(auds.PrivKey(), uploadSigBytes)
uploads, _ := auds.Mediorum.UploadFile(ctx, audioFile, "my-track.mp3", &mediorum.UploadOptions{
Template: "audio", Signature: uploadSignature,
WaitForTranscode: true, WaitForFileUpload: true, OriginalCID: fileCID,
})
transcodedCID := uploads[0].GetTranscodedCID()
signerAddress := auds.Address()
entityID := time.Now().UnixNano() % 1000000
if entityID < 0 {
entityID = -entityID
}
metadata := map[string]interface{}{
"cid": "", "access_authorities": []string{signerAddress},
"data": map[string]interface{}{
"title": "Gated Track", "genre": "Electronic",
"release_date": time.Now().Format("2006-01-02"),
"track_cid": transcodedCID, "owner_id": 1,
},
}
metadataJSON, _ := json.Marshal(metadata)
manageEntity := &corev1.ManageEntityLegacy{
UserId: 1, EntityType: "Track", EntityId: entityID, Action: "Create",
Metadata: string(metadataJSON), Nonce: fmt.Sprintf("0x%064x", entityID), Signer: "",
}
cfg := &config.Config{
AcdcEntityManagerAddress: config.ProdAcdcAddress,
AcdcChainID: config.ProdAcdcChainID,
}
server.SignManageEntity(cfg, manageEntity, auds.PrivKey())
stx := &corev1.SignedTransaction{
RequestId: uuid.NewString(),
Transaction: &corev1.SignedTransaction_ManageEntity{ManageEntity: manageEntity},
}
_, err := auds.Core.SendTransaction(ctx, connect.NewRequest(&corev1.SendTransactionRequest{
Transaction: stx,
}))
return transcodedCID, entityID, err
}
```
Run with `go run . -validator creatornode.audius.co`. Test with `curl -L http://localhost:8800/stream`.
---
## Geo-gated example: Bozeman only
The following examples implement a track that is only streamable in **Bozeman, Montana**. Requests from other regions receive 403 Forbidden. Both Cloudflare Workers and Vercel expose request geo (city, region, country) that we check before signing.
---
## Deploy to edge: Cloudflare Worker
Cloudflare Worker — Bozeman-only stream
Deploy an access server on Cloudflare Workers. Uses `request.cf?.city` for geo. Only signs and redirects when the request originates from Bozeman; otherwise returns 403.
```typescript
// worker.ts - Cloudflare Worker (Bozeman-only)
export interface Env {
SIGNER_PRIVATE_KEY: string;
NODE_BASE_URL: string;
TRACK_ID: string;
CID: string;
}
interface SignatureData {
upload_id: string;
cid: string;
shouldCache: number;
timestamp: number;
trackId: number;
userId: number;
}
function ethSignedMessageHash(msgBytes: Uint8Array): Uint8Array {
const prefix = new TextEncoder().encode(
`\x19Ethereum Signed Message:\n${msgBytes.length}`,
);
return keccak256(concatBytes(prefix, msgBytes));
}
function signStreamUrl(env: Env): string {
const data: SignatureData = {
upload_id: "",
cid: env.CID,
shouldCache: 0,
timestamp: Date.now(),
trackId: parseInt(env.TRACK_ID, 10),
userId: 0,
};
const canonical = canonicalize(data);
if (!canonical) throw new Error("canonicalize failed");
const hash = keccak256(utf8ToBytes(canonical));
const hashToSign = ethSignedMessageHash(hash);
const pk = hexToBytes(
env.SIGNER_PRIVATE_KEY.replace(/^0x/, "").padStart(64, "0"),
);
const sig = secp256k1.sign(hashToSign, pk);
const r = sig.r;
const s = sig.s;
let v = sig.recovery + 27;
const sigHex =
"0x" +
r.toString(16).padStart(64, "0") +
s.toString(16).padStart(64, "0") +
v.toString(16).padStart(2, "0");
const envelope = JSON.stringify({
data: JSON.stringify(data),
signature: sigHex,
});
return `${env.NODE_BASE_URL}/tracks/stream/${env.TRACK_ID}?signature=${encodeURIComponent(envelope)}`;
}
export default {
async fetch(req: Request, env: Env): Promise {
const url = new URL(req.url);
if (url.pathname === "/stream") {
const city = (req as Request & { cf?: { city?: string } }).cf?.city ?? "";
if (!city.toLowerCase().includes("bozeman")) {
return new Response(
JSON.stringify({
error: "Stream only available in Bozeman, Montana",
}),
{
status: 403,
headers: { "Content-Type": "application/json" },
},
);
}
const streamUrl = signStreamUrl(env);
return Response.redirect(streamUrl, 302);
}
return new Response("Not found", { status: 404 });
},
};
```
Deploy with `wrangler deploy`. Set `SIGNER_PRIVATE_KEY`, `NODE_BASE_URL`, `TRACK_ID`, and `CID` as secrets.
---
## Deploy to edge: Vercel Serverless
Vercel Serverless Function — Bozeman-only stream
Deploy as a Vercel serverless function. Uses `request.geo?.city` (from Vercel Edge) for geo. Only signs and redirects when the request originates from Bozeman; otherwise returns 403.
```typescript
// api/stream/route.ts - Vercel App Router (Bozeman-only)
export const runtime = "edge"; // req.geo requires Edge
interface SignatureData {
upload_id: string;
cid: string;
shouldCache: number;
timestamp: number;
trackId: number;
userId: number;
}
function ethSignedMessageHash(msgBytes: Uint8Array): Uint8Array {
const prefix = new TextEncoder().encode(
`\x19Ethereum Signed Message:\n${msgBytes.length}`,
);
return keccak256(concatBytes(prefix, msgBytes));
}
function generateSignature(
cid: string,
trackId: number,
privateKeyHex: string,
): string {
const data: SignatureData = {
upload_id: "",
cid,
shouldCache: 0,
timestamp: Date.now(),
trackId,
userId: 0,
};
const canonical = canonicalize(data);
if (!canonical) throw new Error("canonicalize failed");
const hash = keccak256(utf8ToBytes(canonical));
const hashToSign = ethSignedMessageHash(hash);
const pk = hexToBytes(privateKeyHex.replace(/^0x/, "").padStart(64, "0"));
const sig = secp256k1.sign(hashToSign, pk);
const r = sig.r;
const s = sig.s;
let v = sig.recovery + 27;
const sigHex =
"0x" +
r.toString(16).padStart(64, "0") +
s.toString(16).padStart(64, "0") +
v.toString(16).padStart(2, "0");
return encodeURIComponent(
JSON.stringify({ data: JSON.stringify(data), signature: sigHex }),
);
}
export async function GET(req: NextRequest) {
const pk = process.env.SIGNER_PRIVATE_KEY;
const baseUrl = process.env.NODE_BASE_URL;
const trackId = process.env.TRACK_ID;
const cid = process.env.CID;
if (!pk || !baseUrl || !trackId || !cid) {
return NextResponse.json({ error: "Missing config" }, { status: 500 });
}
const city = req.geo?.city ?? "";
if (!city.toLowerCase().includes("bozeman")) {
return NextResponse.json(
{ error: "Stream only available in Bozeman, Montana" },
{ status: 403 },
);
}
const sig = generateSignature(cid, parseInt(trackId, 10), pk);
const streamUrl = `${baseUrl}/tracks/stream/${trackId}?signature=${sig}`;
return NextResponse.redirect(streamUrl, 302);
}
```
Install: `npm install @noble/hashes @noble/curves/secp256k1 canonicalize`. Set `SIGNER_PRIVATE_KEY`, `NODE_BASE_URL`, `TRACK_ID`, and `CID` in Vercel environment variables. Deploy with Edge Runtime (included above) so `req.geo` is available.
---
## Next steps
- Use multiple `access_authorities` if you have several signing servers
- For DDEX/ERN-based tracks, use `GetStreamURLs` instead of the track signature format—see `examples/programmable-distribution-ddex` in go-openaudio
---
> Source: https://docs.audius.co/tutorials/launch-artist-coins

# Launch Artist Coins
Launch your own Artist Coin and own the economy of your brand. Artist Coins are Solana tokens paired against \$AUDIO with a bonding curve—fair launch, instant trading, graduation to AMM at 1M \$AUDIO market cap. See [Artist Coins](/concepts/artist-coins) for the full tokenomics and flywheel.
## What You Need
| Parameter | Required | Description |
| ----------------------- | -------- | ------------------------------------------------------- |
| `name` | Yes | Coin name (e.g. "Artist Name Coin") |
| `symbol` | Yes | Ticker (e.g. "ARTIST") |
| `description` | Yes | Short description for metadata |
| `image` | Yes | Square image (PNG, up to 1000×1000) |
| `creatorWallet` | Yes | Solana public key of the artist |
| `initialBuyAmountAudio` | No | Optional first buy in \$AUDIO (raw amount, no decimals) |
All Artist Coins share the same curve config: 1B supply, 9 decimals, paired to \$AUDIO, 50% of supply to creator vesting over 5 years, graduation at 1M \$AUDIO.
## Launch Flow
1. **Metadata** — Build token metadata (name, symbol, description, image URL). Upload image and JSON to a decentralized store (e.g. Irys/Arweave).
2. **Config** — Create a bonding curve config on Meteora's Dynamic Bonding Curve program. The config defines the curve shape, graduation threshold, vesting schedule, and fee split.
3. **Pool + First Buy** — Create the pool with your mint and config, then optionally execute the creator's first buy in the same flow.
4. **Reward Pool** — Create a reward pool for the coin (used for fan campaigns and programmable distribution).
The pool is created with your wallet as `poolCreator`, so you own the launch. The config is created by an authorized launchpad partner that has integrated with the OAP infrastructure.
## TypeScript Sketch
Using `@meteora-ag/dynamic-bonding-curve-sdk` and Solana web3, the structure looks like:
```typescript
const AUDIO_MINT = new PublicKey(
"9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM",
);
// 1. Create config (curve design, graduation, vesting)
const configKeypair = Keypair.generate();
const createConfigTx = await dbcClient.partner.createConfig({
payer: authorityKeypair.publicKey,
config: configKeypair.publicKey,
quoteMint: AUDIO_MINT,
tokenSupply: {
preMigrationTokenSupply: new BN("1000000000000000000"),
postMigrationTokenSupply: new BN("1000000000000000000"),
},
tokenDecimal: 9,
sqrtStartPrice: new BN("58333726687135160"),
curve: [
{ sqrtPrice: new BN("49454822942198928"), liquidity: new BN("316151256542714767379182355742720") },
{ sqrtPrice: new BN("67463150367941992"), liquidity: new BN("1034142099640443506675420695101440") },
{ sqrtPrice: new BN("81589054430670160"), liquidity: new BN("1993482779222476007980168292335616") },
{ sqrtPrice: new BN("93607002666542880"), liquidity: new BN("3426004632472395272977352402927616") },
{ sqrtPrice: new BN("104248587969034256"), liquidity: new BN("5527075263389051486360336718626816") },
{ sqrtPrice: new BN("113900242488156192"), liquidity: new BN("8548850210158313829039805609017344") },
{ sqrtPrice: new BN("122795612235063552"), liquidity: new BN("12820672237426984956983602256543744") },
{ sqrtPrice: new BN("131088746769154048"), liquidity: new BN("18771222513185901677116104399388672") },
{ sqrtPrice: new BN("138887568467558320"), liquidity: new BN("26955439896665360498417791527813120") },
{ sqrtPrice: new BN("146271165374977472"), liquidity: new BN("38087728759187777947138400779763712") },
{ sqrtPrice: new BN("153299546527304896"), liquidity: new BN("53082960253944572826836697362726912") },
{ sqrtPrice: new BN("160019524155024992"), liquidity: new BN("73107000099851162532571430724304896") },
{ sqrtPrice: new BN("166468451233631424"), liquidity: new BN("99638821061952779942546313536602112") },
{ sqrtPrice: new BN("172676699069235264"), liquidity: new BN("134546659677401790102417975826972672") },
{ sqrtPrice: new BN("178669358164097472"), liquidity: new BN("180181158020974273473618784576077824") },
{ sqrtPrice: new BN("184467440737095520"), liquidity: new BN("239489003397306076375008380274606080") },
],
migrationQuoteThreshold: new BN("20000000000000"), // ~1M AUDIO graduation
lockedVesting: {
amountPerPeriod: new BN(500_000_000)
.mul(new BN(10).pow(new BN(9)))
.div(new BN(5 * 365)),
frequency: new BN(24 * 60 * 60),
numberOfPeriod: new BN(5 * 365),
},
creatorTradingFeePercentage: 50,
partnerLockedLpPercentage: 50,
creatorLockedLpPercentage: 50,
});
// 2. Create pool + optional first buy
const mintKeypair = Keypair.generate();
const { createPoolTx, swapBuyTx } = await dbcClient.pool.createPoolWithFirstBuy(
{
createPoolParam: {
config: configKeypair.publicKey,
name: "My Coin",
symbol: "MYCOIN",
uri: metadataUri,
poolCreator: creatorWallet,
baseMint: mintKeypair.publicKey,
payer: creatorWallet,
},
firstBuyParam: initialBuyAmountAudio
? { buyer: creatorWallet, buyAmount: new BN(initialBuyAmountAudio) }
: undefined,
},
);
```
The creator signs `createPoolTx` (and `swapBuyTx` if doing an initial buy). After the pool is live, a reward pool is created for the mint.
## Where to Launch
The [Audius launchpad](https://audius.co/coins) provides a UI to launch coins. Because the infrastructure is open—Meteora DBC, Solana, reward pools—any app can integrate. Build your own frontend or automation, or use existing tools.
> **Tip:** Artist Coins graduate to an AMM when they hit 1M \$AUDIO market cap. Until then, they trade on the bonding curve. Post-graduation, 50% of supply vests to the artist linearly over 5 years.
---
> Source: https://docs.audius.co/tutorials/create-reward-pools

# Create Reward Pools
Reward pools are programmable token distributions on the Open Audio Protocol. You can use them for any Solana SPL token, but they are designed for Artist Coins—letting creators create pools that reward fans (e.g. for listening, sharing, or participating in campaigns). Claim authorities sign attestations; users redeem those attestations to claim tokens from the pool.
## Prerequisites
- Go 1.21+
- A node endpoint (e.g. devnet `https://node1.oap.devnet` or production)
- An Ethereum secp256k1 private key (will act as a claim authority)
---
## Create a reward pool
Create a reward by sending a `CreateReward` transaction. You define the reward amount, claim authorities (wallets that can issue attestations), and a deadline block height.
```go
package main
"context"
"fmt"
"log"
"os"
"connectrpc.com/connect"
v1 "github.com/OpenAudio/go-openaudio/pkg/api/core/v1"
"github.com/OpenAudio/go-openaudio/pkg/common"
"github.com/OpenAudio/go-openaudio/pkg/sdk"
)
func main() {
privKeyStr := os.Getenv("PRIVATE_KEY")
if privKeyStr == "" {
log.Fatal("PRIVATE_KEY environment variable is required")
}
privKey, err := common.EthToEthKey(privKeyStr)
if err != nil {
log.Fatalf("failed to parse private key: %v", err)
}
nodeURL := "https://node1.oap.devnet"
oap := sdk.NewOpenAudioSDK(nodeURL)
oap.SetPrivKey(privKey)
ctx := context.Background()
// Get current block height for deadline
status, err := oap.Core.GetStatus(ctx, connect.NewRequest(&v1.GetStatusRequest{}))
if err != nil {
log.Fatalf("failed to get chain status: %v", err)
}
currentHeight := status.Msg.ChainInfo.CurrentHeight
deadline := currentHeight + 100 // e.g. valid for ~100 blocks
// Create the reward pool
reward, err := oap.Rewards.CreateReward(ctx, &v1.CreateReward{
RewardId: "fan-drop-001",
Name: "Fan Drop Campaign",
Amount: 1000, // in token's smallest units
ClaimAuthorities: []*v1.ClaimAuthority{
{Address: oap.Address(), Name: "Campaign Admin"},
},
DeadlineBlockHeight: deadline,
})
if err != nil {
log.Fatalf("failed to create reward: %v", err)
}
fmt.Printf("reward pool created at address: %s\n", reward.Address)
}
```
| Field | Description |
|-------|-------------|
| `RewardId` | Unique identifier for this reward (e.g. campaign code) |
| `Name` | Human-readable name |
| `Amount` | Reward amount per claim, in the token's smallest units |
| `ClaimAuthorities` | Wallets that can sign attestations to authorize claims |
| `DeadlineBlockHeight` | Block height after which the pool expires |
---
## Get reward details
Fetch an existing reward by its on-chain address:
```go
reward, err := oap.Rewards.GetReward(ctx, rewardAddress)
if err != nil {
log.Fatalf("failed to get reward: %v", err)
}
fmt.Printf("reward id: %s, amount: %d\n", reward.RewardId, reward.Amount)
```
---
## Issue an attestation
When a user qualifies (e.g. completes a listen, shares, or meets your criteria), a claim authority signs an attestation. The user submits that attestation to claim tokens.
```go
recipient := "0x1234..." // Ethereum address of the recipient
amount := uint64(1000)
specifier := "user_123_listen" // Unique per claim (e.g. user + action)
attestation, err := oap.Rewards.GetRewardAttestation(ctx, &v1.GetRewardAttestationRequest{
EthRecipientAddress: recipient,
Amount: amount,
RewardAddress: reward.Address,
RewardId: "fan-drop-001",
Specifier: specifier,
ClaimAuthority: oap.Address(),
AmountDecimals: 9, // match the reward token's decimals
})
if err != nil {
log.Fatalf("failed to get attestation: %v", err)
}
// Give attestation.Attestation to the user—they use it to claim
fmt.Printf("attestation: %s\n", attestation.Attestation)
```
The claim authority's private key is used to sign the claim. Your server holds that key and issues attestations only when your logic (auth, eligibility, rate limits) allows. The `Specifier` uniquely identifies each claim so the same user cannot redeem the same attestation twice.
---
## Next steps
- Link reward pools to Artist Coins: when you [launch an Artist Coin](/tutorials/launch-artist-coins), a reward pool is created for that mint. You can create additional pools for campaigns.
- Use the attestation in your claim flow: the user submits the signed attestation to the rewards program to receive tokens.
---
> Source: https://docs.audius.co/reference/ethereum-contracts
# Ethereum Contracts
> **Info:** All contracts are built on top of
> [OpenZeppelin's Proxy pattern](https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies) through
> `Open AudioAdminUpgradeabilityProxy` which extends `AdminUpgradeabilityProxy`, enabling logic upgrades
> to be performed through the [Governance contract](#governance).
> **Note:** All contracts are fully audited by reputable security firms, see [Audits](/reference/audits) for more details.
## Contracts
[github.com/OpenAudio/eth-contracts](https://github.com/OpenAudio/eth-contracts)
### $AUDIO
The `$AUDIO` token is a ERC-20 token contract with initial supply of 1 billion tokens, each
divisible up to 18 decimal places, and is `Mintable`, `Pausable`, and `Burnable`.
| Mainnet Contract | Sepolia Testnet Contract |
| ----------------------------------------------------- | ---------------------------------------------------------- |
| [`0x18aAA7115705e8be94bfFEBDE57Af9BFc265B998`][Token] | [`0x1376180Ee935AA64A27780F4BE97726Df7B0e2B2`][Token_test] |
[Token]: https://etherscan.io/address/0x18aAA7115705e8be94bfFEBDE57Af9BFc265B998#code
[Token_test]: https://sepolia.etherscan.io/address/0x1376180Ee935AA64A27780F4BE97726Df7B0e2B2
### ClaimsManager
> This contract is responsible for allocating and minting new tokens as well as managing claim
> rounds.
A claim round is a period of time during whic node operators with valid stakes can retrieve the
reward apportioned to them in the network. Claims are processed here and new value transferred to
[Staking](#staking) for the claimer, but the values in both
[ServiceProviderFactory](#serviceproviderfactory) and [DelegateManager](#delegatemanager) are
updated through calls to [DelegateManager](#delegatemanager).
| Mainnet Contract | Sepolia Testnet Contract |
| ------------------------------------------------------------- | ------------------------------------------------------------------ |
| [`0x44617F9dCEd9787C3B06a05B35B4C779a2AA1334`][ClaimsManager] | [`0xcdFFAE230aeDC376478b16f369489A3b450fc2c8`][ClaimsManager_test] |
[ClaimsManager]: https://etherscan.io/address/0x44617F9dCEd9787C3B06a05B35B4C779a2AA1334#code
[ClaimsManager_test]: https://sepolia.etherscan.io/address/0xcdFFAE230aeDC376478b16f369489A3b450fc2c8
### DelegateManager
> This contract is responsible for tracking delegation state, making claims, and handling slash
> operations.
This contract allows any Audio Token holder to delegate to an existing Node Operator, earning
rewards by providing additional stake the Node Operator while allocating a known percentage of their
rewards to the Node Operator.
This contract manages the proportional distribution of stake between the Node Operator and
delegators.
All claim and slash operations flow through this contract in order to update values tracked outside
of the [Staking contract](#staking) appropriately and maintain consistency between total value
within the [Staking contract](#staking) and value tracked by the
[DelegateManager contract](#delegatemanager) and the
[ServiceProviderFactory contract](#serviceproviderfactory).
| Mainnet Contract | Sepolia Testnet Contract |
| --------------------------------------------------------------- | -------------------------------------------------------------------- |
| [`0x4d7968ebfD390D5E7926Cb3587C39eFf2F9FB225`][DelegateManager] | [`0xDA74d6FfbF268Ac441404f5a61f01103451E8697`][DelegateManager_test] |
[DelegateManager]: https://etherscan.io/address/0x4d7968ebfD390D5E7926Cb3587C39eFf2F9FB225#code
[DelegateManager_test]: https://sepolia.etherscan.io/address/0xDA74d6FfbF268Ac441404f5a61f01103451E8697
### EthRewardsManager
| Mainnet Contract | Sepolia Testnet Contract |
| ----------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [`0x5aa6B99A2B461bA8E97207740f0A689C5C39C3b0`][EthRewardsManager] | [`0x563483ccD66a49Ca730275F8cf37Dd3E6Da864f1`][EthRewardsManager_test] |
[EthRewardsManager]: https://etherscan.io/address/0x5aa6B99A2B461bA8E97207740f0A689C5C39C3b0#code
[EthRewardsManager_test]: https://sepolia.etherscan.io/address/0x563483ccD66a49Ca730275F8cf37Dd3E6Da864f1
### Governance
> This contract allows protocol participants to change protocol direction by submitting and voting
> on proposals.
Each proposal represents an executable function call on a contract in the [Registry](#registry).
Once submitted, there is a period of time during which other participants can submit their votes -
`Yes` or `No` - on the proposal.
After the voting period has concluded, the proposal outcome is calculated as a the sum of the stakes
of the `Yes` voters minus the sum of the stakes of the `No` voters.
Any non-negative value results in a successful proposal, at which point the specified function call
is executed, and the proposal is closed.
Only addresses that have staked in [Staking.sol](#staking) and are represented through the
[Registry](#registry) can submit and vote on proposals.
| Mainnet Contract | Sepolia Testnet Contract |
| ---------------------------------------------------------- | --------------------------------------------------------------- |
| [`0x4DEcA517D6817B6510798b7328F2314d3003AbAC`][Governance] | [`0x04973b4416f7e3D62374Ef8b5ABD4a98e4dD401C`][Governance_test] |
[Governance]: https://etherscan.io/address/0x4DEcA517D6817B6510798b7328F2314d3003AbAC#code
[Governance_test]: https://sepolia.etherscan.io/address/0x04973b4416f7e3D62374Ef8b5ABD4a98e4dD401C
### Registry
Contract through which external clients and Governance interact with the remaining contracts within
the protocol. Each contract is registered using a key with which its address can be queried.
| Mainnet Contract | Sepolia Testnet Contract |
| -------------------------------------------------------- | ------------------------------------------------------------- |
| [`0xd976d3b4f4e22a238c1A736b6612D22f17b6f64C`][Registry] | [`0xc682C2166E11690B64338e11633Cb8Bb60B0D9c0`][Registry_test] |
[Registry]: https://etherscan.io/address/0xd976d3b4f4e22a238c1A736b6612D22f17b6f64C#code
[Registry_test]: https://sepolia.etherscan.io/address/0xc682C2166E11690B64338e11633Cb8Bb60B0D9c0
### ServiceProviderFactory
> This contract is responsible for tracking Service Provider state within the Open Audio network.
A service provider is the account associated with a given service endpoint.
Each service provider can increase/decrease stake within dynamic bounds determined by the
combination of endpoints they have registered, accept delegation from other token holders, define a
reward cut for delegation, and continue registering endpoints as necessary.
This contract forwards staking requests to the actual [Staking](#staking) contract but tracks the
amount of stake for the deployer - [Staking](#staking) tracks the sum of delegate stake + deployer
stake.
| Contract | Sepolia Testnet Contract Mainnet |
| ---------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [`0xD17A9bc90c582249e211a4f4b16721e7f65156c8`][ServiceProviderFactory] | [`0x377BE01aD31360d0DFB16035A4515954395A8185`][ServiceProviderFactory_test] |
[ServiceProviderFactory]: https://etherscan.io/address/0xD17A9bc90c582249e211a4f4b16721e7f65156c8#code
[ServiceProviderFactory_test]: https://sepolia.etherscan.io/address/0x377BE01aD31360d0DFB16035A4515954395A8185
A Note on Terminology
Through out the Smart Contracts that define the Open Audio protocol, the term "service provider" is used
along with the "service endpoint(s)" that they operate.
More current terminology is "Node Operator" and "Open Audio Node" respectively.
- **Old**: `Service Providers` operate `service endpoints`
- **New**: `Node Operators` operate `Open Audio Nodes`
### ServiceTypeManager
> This contract is responsible for maintaining known `service types`, associated versioning
> information and service type stake requirements within the Open Audio Protocol.
Service types are used to identify services being registered within the protocol, for example
`validator`.
Service type stake requirements enforce a minimum and maximum stake amount for each endpoint of a
given type that service providers register.
| Mainnet Contract | Sepolia Testnet Contract |
| ------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| [`0x9EfB0f4F38aFbb4b0984D00C126E97E21b8417C5`][ServiceTypeManager] | [`0x9fd76d2cD48022526F3a164541E6552291F4a862`][ServiceTypeManager_test] |
[ServiceTypeManager]: https://etherscan.io/address/0x9EfB0f4F38aFbb4b0984D00C126E97E21b8417C5#code
[ServiceTypeManager_test]: https://sepolia.etherscan.io/address/0x9fd76d2cD48022526F3a164541E6552291F4a862
A Note on Terminology
Through out the Smart Contracts that define the Open Audio protocol, the terms `creator-node` and
`discovery-provider` are used to define `service types`.
More current terminology is `content-node` and `discovery-node` are `Open Audio Node` types
respectively.
- **Old**: `creator-node` and `discovery-provider` are `service types`
- **New**: `content-node` and `discovery-node` are `Open Audio Node` types.
### Staking
> This contract manages token staking functions and state across the Open Audio Protocol
For every service provider address in Open Audio Protocol, this contract:
- Stores tokens and manages account balances
- Tracks total stake history
- The total stake (represented as the sun of the deployer stake plus the delegate stake)
- Tracks last claim block
| Mainnet Contract | Sepolia Testnet Contract |
| ------------------------------------------------------- | ------------------------------------------------------------ |
| [`0xe6D97B2099F142513be7A2a068bE040656Ae4591`][Staking] | [`0x5bcF21A4D5Bab9B0869B9c55D233f80135C814C6`][Staking_test] |
[Staking]: https://etherscan.io/address/0xe6D97B2099F142513be7A2a068bE040656Ae4591#code
[Staking_test]: https://sepolia.etherscan.io/address/0x5bcF21A4D5Bab9B0869B9c55D233f80135C814C6
### TrustedNotifierManager
This contract serves as the on chain registry of trusted notifier services. Other services may look
up and adjust their selected trusted notifier accordingly.
| Contract | Sepolia Testnet Contract Mainnet |
| ---------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [`0x6f08105c8CEef2BC5653640fcdbBE1e7bb519D39`][TrustedNotifierManager] | [`0x71f8D2aC2f63A481d597d2A6cc160787A048525C`][TrustedNotifierManager_test] |
[TrustedNotifierManager]: https://etherscan.io/address/0x6f08105c8CEef2BC5653640fcdbBE1e7bb519D39#code
[TrustedNotifierManager_test]: https://sepolia.etherscan.io/address/0x71f8D2aC2f63A481d597d2A6cc160787A048525C
### WormholeClient
This contract serves as the interface between the Open Audio Protocol and
[Wormhole](https://solana.com/ecosystem/wormhole).
| Mainnet Contract | Sepolia Testnet Contract |
| -------------------------------------------------------------- | ------------------------------------------------------------------- |
| [`0x6E7a1F7339bbB62b23D44797b63e4258d283E095`][wormholeclient] | [`0x2Eb3BF862e7a724A151e78CEB1173FB332E174a0`][wormholeclient_test] |
[wormholeclient]: https://etherscan.io/address/0x6E7a1F7339bbB62b23D44797b63e4258d283E095#code
[wormholeclient_test]: https://sepolia.etherscan.io/address/0x2Eb3BF862e7a724A151e78CEB1173FB332E174a0
---
> Source: https://docs.audius.co/reference/solana-programs
## Programs
Please note that all "Testnet" Programs are deployed to Solana Mainnet. This is to allow for proper liquidity on all functionality.
> [github.com/OpenAudio/solana-programs](https://github.com/OpenAudio/solana-programs)
> **Note:** All programs are fully audited by reputable security firms, see [Audits](/reference/audits) for more details.
### Claimable Tokens
This program powers the Audius reward system, and allows Solana non-custodial token accounts to be created for Audius users that are represented by an Ethereum wallet. This program is also referred to as the "User Bank".
This program was developed originally to allow for a very specific use case: make it so users who only have ethereum accounts can receive solana token rewards. Leveraging the solana secp instructions, we derive token accounts belonging to a PDA on the claimable tokens program whose ability to transfer from is controlled by ethereum signatures. This allows for us to send rewards to a user and for them to be later claimed without needing them to even have an active session to create a receiving wallet.
| Mainnet Program | Testnet Program |
| ----------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [`Ewkv3JahEFRKkcJmpoKB7pXbnUHwjAyXiwEo4ZY2rezQ`][ClaimableTokens] | [`2sjQNmUfkV6yKKi4dPR8gWRgtyma5aiymE3aXL2RAZww`][ClaimableTokens_test] |
[ClaimableTokens]: https://solscan.io/account/Ewkv3JahEFRKkcJmpoKB7pXbnUHwjAyXiwEo4ZY2rezQ
[ClaimableTokens_test]: https://solscan.io/account/2sjQNmUfkV6yKKi4dPR8gWRgtyma5aiymE3aXL2RAZww
### Payment Router
This program is responsible for distributing any SPL Token across different provided Solana
associated token accounts, with amounts determined by a given percent split.
It is intended to be used with `SPL-AUDIO` and `SPL-USDC`. While payments can be made independently
of the `Payment Router` program, it is designed to improve space-efficiency and usability off-chain.
| Mainnet Program | Testnet Program |
| -------------------------------------------------------------- | ------------------------------------------------------------------- |
| [`paytYpX3LPN98TAeen6bFFeraGSuWnomZmCXjAsoqPa`][PaymentRouter] | [`sp28KA2bTnTA4oSZ3r9tTSKfmiXZtZQHnYYQqWfUyVa`][PaymentRouter_test] |
[PaymentRouter]: https://solscan.io/account/paytYpX3LPN98TAeen6bFFeraGSuWnomZmCXjAsoqPa
[PaymentRouter_test]: https://solscan.io/account/sp28KA2bTnTA4oSZ3r9tTSKfmiXZtZQHnYYQqWfUyVa
### Reward Manager
This program allows for Audius users to claim rewards given attestations from Node Operators that
they have successfully completed a challenge.
For example, to claim the “I've completed my profile reward,” a user may ask for a set of Discovery
Node Operators to provide a cryptographic proof that they have completed the challenge and submit
that to chain. If the signatures are valid, tokens are dispensed
| Mainnet Program | Testnet Program |
| --------------------------------------------------------------- | -------------------------------------------------------------------- |
| [`DDZDcYdQFEMwcu2Mwo75yGFjJ1mUQyyXLWzhZLEVFcei`][RewardManager] | [`CDpzvz7DfgbF95jSSCHLX3ERkugyfgn9Fw8ypNZ1hfXp`][RewardManager_test] |
[RewardManager]: https://solscan.io/account/DDZDcYdQFEMwcu2Mwo75yGFjJ1mUQyyXLWzhZLEVFcei
[RewardManager_test]: https://solscan.io/account/CDpzvz7DfgbF95jSSCHLX3ERkugyfgn9Fw8ypNZ1hfXp
### Staking Bridge
This program has 2 main functions:
1. Swap `SPL-USDC` tokens to `SPL-AUDIO` tokens via the [Raydium AMM Program](https://raydium.io/).
2. Convert `SPL-AUDIO` tokens to `ERC20-AUDIO` tokens via the Wormhole Token Bridge.
The methods in this program are intentionally permissionless, allowing any user willing to pay
transaction fees to interact.
The methods of the Staking Bridge are independent to reduce price impact of swaps and fees
associated with bridging tokens.
| Mainnet Program | Testnet Program |
| -------------------------------------------------------------- | ------------------------------------------------------------------- |
| [`stkB5DZziVJT1C1VmzvDdRtdWxfs5nwcHViiaNBDK31`][StakingBridge] | [`stkuyR7dTzxV1YnoDo5tfuBmkuKn7zDatimYRDTmQvj`][StakingBridge_test] |
[StakingBridge]: https://solscan.io/account/stkB5DZziVJT1C1VmzvDdRtdWxfs5nwcHViiaNBDK31
[StakingBridge_test]: https://solscan.io/account/stkuyR7dTzxV1YnoDo5tfuBmkuKn7zDatimYRDTmQvj
---
> Source: https://docs.audius.co/reference/audits
# Audits
All Smart Contracts & Programs part of the Open Audio and Audius ecosystem are fully audited by reputable security firms.
Zellic Claimable Tokens and Rewards Program
- November 11, 2025
Neodyme Payment Router
- November 27, 2023
Neodyme Staking Bridge
- November 27, 2023
Zellic Solana Programs
- November 21, 2022
Zellic EVM
- October 11, 2022
Kudelski Solana Programs
- February 7, 2022
OpenZeppelin Ethereum Contracts
- August 25, 2020
We welcome any and all responsible bug / vulnerability disclosure. Please see our bug bounty policy at
[openaudio.org/security](https://openaudio.org/security).
---
> Source: https://docs.audius.co/reference/open-music-license
# Open Music License
The Open Music License (OML) governs how music on the Open Audio Protocol can be used by developers and artists. It's designed to make building on the protocol straightforward, while keeping artists in control of their music.
[You can read it here (it's quite short!).](https://openaudiofoundation.org/open-music-license.pdf)
---
> Source: https://docs.audius.co/blog/introducing-the-open-audio-protocol

# Introducing The Open Audio Protocol
Today marks the most significant milestone for the infrastructure that powers Audius since its public launch in 2020. We're introducing version 1.0 of the protocol, now reimagined as the Open Audio Protocol [openaudio.org](https://openaudio.org), secured by $AUDIO.
When we began building Audius nearly seven years ago, our vision was to create an open, interoperable, and free-to-use global backend for music. We wanted to build a protocol that would stand the test of time and empower artists to own not just their music, but its distribution and their relationships with their fans, free from centralized gatekeepers. Since then, we're incredibly proud to have turned that vision into reality, having served nearly 400 million streams across apps built on top of the music catalog.
With the launches this week around Artist Coins, we believe that the state of the Audius ecosystem is at a level of completeness that we can “coin” it the official 1.0 release (currently, it runs at 0.7). In fact, Artist Coins are the last missing piece of the puzzle proposed in the original 2020 Audius Whitepaper ([whitepaper.audius.co](https://whitepaper.audius.co), section 2.3), making the protocol feature complete. Of course, at that time, we could never have dreamed of how we could build it today. A lot has evolved in the creator and meme coin space over the years that makes a launch exactly like ours possible (Wallet UX, Solana, Meteora/Jupiter, Metaplex, RPC infra etc.), but this launch has always been part of the plan.
While we could not be more excited about the positioning of Audius in this next chapter, one thing stood out to us to address head on: our brand positioning.
Over the years, Audius has become much more commonly known as a music app, especially because our audience is primarily dominated by non-Web3-native artists and fans. Within the crypto space, the conventional wisdom at blockchain conferences is that Audius was a 2021 era music NFT play. And while it is true that we built some awesome integrations in the collectible space before anyone else did (NFT gated releases, NFT profile pictures, etc.), that was never the end goal. The technology behind Audius goes far beyond a single music app and has continued to grow in both usage and functionality year over year.
So, today marks not only the 1.0 release of the protocol, but the rebranding of the protocol stack as the Open Audio Protocol, along with a few brand new bells and whistles:
* A consolidation of the two node types (discovery node and content node) into one single validator node (reference implementation: [github.com/openaudio/go-openaudio](https://github.com/openaudio/go-openaudio))
* More cost-effective blob storage streaming capability with higher & more resilient replication
* A stronger rewards system & framework for future work against Solana Artist Coins
* A fast BFT mechanism that keeps the storage network in sync
* Strong interoperability with the DDEX standard (the widely used music industry data format for releases, having partnered with Warner/Chappell Music, Kobalt, Distrokid, and many others)
* Block production and storage proofs to secure the network with automated
slashing recommendations
and lots more.\
(read on at [docs.openaudio.org](https://docs.openaudio.org))
### Why rebrand the protocol?
It's a good question. There is prior art in both directions on this. Dan Romero and the Farcaster team recently dropped the app name Warpcast and unified all their branding under Farcaster. On the other hand, teams like Uniswap have launched new brands such as Unichain for their protocol initiatives, and Circle has done the same with Arc, their L1 chain. What differentiates Audius from many other crypto projects though is that our (app-side) user base is made up of people who do not read “Crypto Twitter” and may even be getting their first introduction to Web3 on our site. This naturally calls for a brand approach that bridges both worlds.
An effort like this runs risks of separating our audiences too far, but it also lets us tackle what is most important and directly in front of us: how do we continue to carve out the path for Audius to be known and loved as the app it is and for the protocol itself to attract new builders to create the Spotify/Tidal/Apple Music killers of 2025? And in fact, we would welcome anyone building a music client/DSP that outpaces Audius in growth, built on the Open Audio Protocol. Seriously, bring it. WAGMI.
Going forward, now that the community has officially adopted these changes ([proposal 157](https://staking.openaudio.org/#/governance/proposal/157)), think about it this way:
[Audius](https://audius.co/) is the music app for the web3 world. Its feature set is robust (chat, rewards, social, and much more) and can be your home base as an artist to connect with new fans. [Seriously, why would you keep using SoundCloud?](https://blog.audius.co/article/compare-audius-vs-soundcloud-the-differences-you-need-to-know)
And
The [Open Audio Protocol](https://openaudio.org) is the global music database, open and boundaryless for all of us who reject the streaming status quo and imagine an internet where music looks more like Tim Berners-Lee's [original vision](https://www.vanityfair.com/news/2018/07/the-man-who-created-the-world-wide-web-has-some-regrets).
And all of the above is powered by **$AUDIO**.
We hope you join us on this endeavor. This is the next chapter, not the finish line. Please let us know what you think: x.com/OpenAudio.
---
> Source: https://docs.audius.co/blog/the-solana-rent-problem
# Claimable Tokens & The Solana Rent UX Problem
> This blog post discusses one of the challenges building on Solana and the recent enhancement to our Claimable Tokens program we have made to address some of it
Giving Web2 native users a straightfoward onramp experience to holding onchain assets is still, in 2025, one of the biggest challenges that the blockchain ecosystem faces in its pursuit of consumer adoption.
### The Onramp Problem
Any project-team building in the Web3 consumer space and trying to attract non-crypto native users likely shares these two goals:
1. Users of the protocol should be able to onboard easily into custodying some onchain asset
2. Assurance that onboarding is somewhat sibyl resistant (one person one account)
Solving for these two goals varies in difficulty depending on what kind of audience your product targets. High-intent user experiences have an easier time (e.g. crypto-banking, RWAs) where users plan to spend a lot, hold a lot, and each individual user can be worth high CAC to the onboarding protocol.
For example, to onramp \$1,000 of crypto in a banking-like app, you would likely need to KYC your customers to check against OFAC, verify authenticity, etc. KYC flows typically cost north of $1/user and have a hefty UX tax: push a user to scan their driver's license or enter their SSN into a webform for an application they're new to. The KYC flow solves for (2) quite well (though forged passports are a real threat), and does enough for (1) because the user is willing to stomach 5 minutes of onboarding because of the size of their purchase/activity. They are _high-intent_.
Products in social, music, etc. typically have a lot of _low-intent_ users, at least the top of the funnel. The user's first experience to these kinds of products may be in an embedded mobile browser inside Instagram or it may be from a friend sharing a one off piece of UGC content in a text message. The user is passive and not necessarily likely to convert, even in the best of situations. For this kind of use case, your product does not have the luxury of 5 minutes of time for the user to enter their SSN. An in-the-trenches crypto native may say, "wait! they can just open your mobile website inside their Phantom browser!" and use their wallet (or something to that end). This naïvely assumes that the user already has a crypto wallet and that the user has already onramped in some capacity. This is a big assumption to make when your product is trying to reach normal adoption.
### Solana Rent
Solana, though still the obvious choice for many consumer use cases due to its fast transaction speeds, full decentralization, and robust ecosystem, has an additional hurdle in the onramping problem: Rent.
The concept around Solana rent is simple: to store data onchain, you need to put up some collateral for it. While this is a bit of a simplification and the history is somewhat more complicated, rent boils down to putting down a one-time payment in order to write data. Everything onchain is stored in accounts on Solana and opening accounts requires a security deposit. When you close the account, you get your deposit back.
This materializes in a very painful way for onramp token custody. In order to hold any amount of a coin (no matter how small an amount), it costs ~0.002 SOL to open the account. At a low price of SOL, this is not a horrible fee to pay, but even at today's depressed prices of \$140/SOL, 0.002 is almost 30 cents! Put in the context of Artist Coins, in order to hold \$1 USD of value of a coin, you pay a 30% tax! (Yes, not exactly "paid" becuase it can get returned, but it's a major friction point.) Noted that most Artist Coin gated tracks only require 1 coin for access, so the rent fee can even outpace the value of the coin itself.
Building in music x crypto, we constantly dream of features and use-cases where artists can airdrop 1 coin of theirs to every fan who has attended a past show or purchased merchandise. That is functionally quite tricky to build with the cost of rent. The rent is just too damn high!
### The Claimable Tokens (User Bank) Program
The cost of rent was an unacceptable onboarding experience of holding \$AUDIO, so in 2022, under the Audius Protocol and now formally adopted in the Open Audio Protocol, a program was designed to pre-allocate solana $AUDIO accounts for users. We called this program the Claimable Tokens (or User Bank) program and you can read more about it [here](/reference/solana-programs.mdx#claimable-tokens).
The concept was to allow for applications on the protocol, like Audius, to be able to open token accounts for users, claimable only to an end-user and for the annoying rent fee to be covered by a fee payer of the application's choosing. Another, more mundane aspect of the original design, was to solve for the fact that at the time all Audius users had Ethereum addresses which are not directly compatible with Solana and the Claimable Tokens program allowed for Ethereum identities to prove ownership over Solana accounts.
Since 2022, the Claimable Tokens program has grown to serve both USDC for in-app cash accounts on Audius as well as all Artist Coin holdings of users. In 2022, when Claimable Tokens launched, 0.002 SOL of rent was around 10 cents. This was a reasonable choice at the time for 1 \$AUDIO account per user, but simply cannot scale to the magnitude of users across the Open Audio Protocol today and the variety of coins they may hold, many of which may have small dollar value (remember, a Spotify stream is worth around 0.3 cents).
### 2025 & Beyond
We have recently shipped a simple, but very useful change to the Claimable Tokens program that allows for ownership to be transfered and accounts to be closed. This solves two major issues with the prior implementation for us.
#### 1. Set Authority
Claimable Token accounts can now be moved from the Claimable Tokens program onto a user's own Solana wallet as associated accounts. This supports a much cleaner experience for crypto-native users who bring wallets to the table already and also allows for balances to be more easily read by applications, explorers, CEXs, and DEXs. All funds belonging to a user can be more easily summed up with much simpler arithmetic.
#### 2. Close Account
Claimable Token accounts can now be closed when their balance is zero and return rent to a predetermined owner when the account was created. This change massively reduces the amount of farming cost incurred by an application wishing to fee pay for their users as farmers/spammers typically churn and those zero'd out accounts can be now swept.
Very notably the native Solana Token Program account model _does not_ support a way to have rent reclaimed by specified authority -- only the owner on the account. There are obvious denial-of-service concerns that are presented with the ability for acounts to be closed by a third-party, but as it stands today, it is prohibitively impossible to fee pay someone else's token account rent using native solana tools as such a system can be infinitely drained of funds.
Imagine that you want to build an app where users can come in and earn revenue in USDC onto their Coinbase account. When the user signs up to earn, they have no earnings yet, but the application needs to create a USDC account on their Coinbase wallet, costing 0.002 SOL. If the application fee-pays the rent for that account creation, a nefarious user could simply close the account and collect the 0.002 SOL and repeat the process. The changes that give our Claimable Tokens program a flexible close account rent recipient, give the application slightly more control over user accounts, but can help prevent such fund draining scenarios from happening.
---
The changes to support these two functionalities can be seen in the [code on Github](https://github.com/OpenAudio/solana-programs/tree/main/claimable-tokens), and thanks to our partners at Zellic, the program has been fully audited and upgraded at [] on Dec 4, 2025.
Zellic Claimable Tokens and Rewards Program
- November 11, 2025
---
This obviously doesn't solve all of the problems we face with sibyl resistance and onboarding UX, but it is a meaningful step in the direction of creating better first onboarding experiences of anyone who is a first time crypto holder.
We welcome any bugs, feature asks and usages of our Claimable Tokens programs or any of the other utilities we have built along the way to bringing music onchain.
---
> Source: https://docs.audius.co/blog/programmable-music-for-agents-and-vibecoders

# Programmable Music for Agents and Vibecoders
You can now build any music experience you want.
> Read https://audius.co/agents.md and build me a music streaming app that looks like Spotify, but streams from the Open Audio Protocol.
Instant and autonomous software, fueled by the "Cambrian Explosion" of coding agents in 2026, sets the stage for an increased utilization of open and block-chain native protocols. The Open Audio Protocol is the largest open and programmable music catalog in existence and is built for this moment: powering vibecoded applications and agent-native music economies.
### Agents Are No Longer Just Chatbots
In recent months, we've gone from "AI assistants" to autonomous systems that can launch complete products, run communities, curate feeds, execute transactions, and collaborate with humans in real time. Agents can now author code, write music, and coordinate capital. And what once required full teams with venture funding to launch startups in music now takes no more than impassioned individuals with great ideas.
Through this chapter, one thing is clear: Agents need infrastructure. They need primitives to build on and interfaces to interact with each other and with humans.
If 2026 is the year agents become ubiquitous, it's also the year we must decide what cultural primitives they are allowed to build with. Music is one of the most powerful datasets on earth. It shapes identity, movements, communities, and economies. But historically, music catalogs have been locked inside platforms: read-only, rate-limited, monetized through opaque policies, and designed primarily for mass-appeal UI.
That model does not adapt to the flexibility of the vibecoder era.
### From Streaming To Programmable Catalogs
The streaming era in music was UI-first.
Platforms like Spotify won by building the best consumer experience. They made music instant and searchable. The infrastructure behind the scenes was powerful, but invisible. It existed to serve a single purpose: deliver a seamless listening interface for humans.
That model made sense when distribution was scarce and such interfaces were expensive to build. But we are no longer in that world.
Content creation is now exponential. Tools like Suno, Udio, SongKit, and modern DAWs can help create music at near-zero marginal cost. Interface creation is also instant. Agentic IDEs like Claude Code and Codex can scaffold, ship, and iterate on full products in hours. When both content and interfaces become cheap, the bottleneck shifts. The one-size-fits-all streaming platform stops being the center of gravity and what matters is not the app, but the dataset beneath it. In 2026, you can be in charge of your own music listening experience because you now have the tools to do so.
In a world of infinite music and infinite interfaces, durable leverage lives in the protocol: who can access the catalog, how rights are expressed, how value flows, and how systems compose.
Agents require
- Machine readable rights and controls
- Composable metadata
- Internet-native financial rails
- Open, permissionless systems
The shift is not just from a Web 2.0 to a Web 3.0, but from a UI-first to an agent-first way of managing information. This requires a fundamentally different technology: a catalog that isn't just consumable, but programmable by its design. A catalog where rights are encoded, access is deterministic, and commerce is composable. Agents require publicly verifiable data and authentic sources, not walled gardens that lack transparency.
### The Open Audio Protocol
The Open Audio Protocol is the largest open, programmable music catalog in existence.
In a world where agents can churn out millions of niche music experiences for human and computer consumers, the backend that succeeds must be permissionless and community governed. Centralized APIs only introduce gatekeeping, monetization choke points, and arbitrary revocation whereas open protocols allow anyone to build, experiment, fork, remix, extend. And by being blockchain-native, governed by $AUDIO, ownership is transparent, payments are programmable, and smart contracts bind interactions to common rules.
The Open Audio Protocol provides novel consumption experiences that are simply not possible with traditional streaming infrastructure that is single-purpose by design.
### The Vibecoded Future
If the Cursor creator can whip up an overnight Rollercoaster Tycoon clone (an incredible feat that famously took 2 years of raw assembly programming), spinning up a custom music interface is no longer a significant undertaking. As individuals consume a unique portfolio of music, the interfaces will follow suit.
One app might be an autonomous ambient radio station for late-night coders. Another might be a token-gated hyperpop fan club with dynamic remix rights. Another might be a genre agent that rewires itself based on your onchain identity.
None of these experiences need to replace each other. They will coexist and all need to draw from the same programmable catalog. The vibecoded future will not be owned by a single app, but instead be composed by thousands of builders and agents experimenting at the edges.
Music is the first great cultural dataset to become programmable at global scale and The Open Audio Protocol is that substrate. If you can imagine a music experience, you can now build it. The only constraint is the catalog you build on.
Music was once physical.
It became digital.
Then it became streamable.
Now it is programmable.
---
> Source: https://docs.audius.co/blog/powering-friends-and-family-disclosure-record-label
# Powering Friends & Family, Disclosure's New Record Label

This week, **Audius and Friends & Family** launched [Crate](https://crate.is), a modern A&R platform designed to help record labels move faster, review demos efficiently, and sign their next breakout records.
In 2026, the volume of music being created is staggering. The next platinum single could emerge from anywhere, and A&R teams are under increasing pressure to discover, evaluate, and move on opportunities quickly. Traditional demo pipelines built around emails, private links, and scattered messages were never designed for this pace.
Crate changes that.
Designed by the Audius team, Crate is a purpose-built A&R workspace that transforms demo submission and review into a structured, high-signal workflow.
Out of the box, Crate gives label teams the tools they need to triage music quickly:
- **Email-free demo submission and listening**
- **Timestamped feedback with voice note support**
- **Keyboard shortcuts for rapid demo triage**
- **Multi-reviewer A&R collaboration**
- **Automated feedback delivery to artists**
- **Release-ready preparation for instant distribution to Audius and other streaming platforms**

But the most powerful part of Crate isn't just the workflow.
It's what's underneath it: The Open Audio Protocol.
### Enter Open Audio
Alongside the launch of Crate, we're introducing a new capability on the **Open Audio Protocol** called [**Programmable Distribution**](/tutorials/gate-release-access). This feature allows developers and platforms to publish music to the network while **cryptographically controlling how and where it can be accessed**.
The Audius product has experimented with many forms of cryptographic access control in the past, gating music by:
- NFT ownership
- Crypto tips & payments ($AUDIO, USDC)
- Social interactions
These systems worked well for specific use cases, but one lesson became clear along the way: Trying to design a protocol interface for every possible access rule isn't scalable. What if an artist wants their single only streamable during a live promotion event? What if an artist wants to geofence where a song can be streamed? What if an artist wants fans to complete challenges to unlock pieces of an album?
Instead, the Open Audio Protocol takes a **developer-first approach**. Rather than defining every rule on-chain, developers can program their own access logic externally while still using the protocol for distribution, indexing, and rights management.
### How Programmable Distribution Works
With Programmable Distribution, a track's metadata transaction can include access authorities: cryptographic addresses authorized to approve playback.
When streaming requests are made, validator nodes in the decentralized network verify that the request includes a valid signature from one of these authorities before serving the audio.
In simpler terms, the protocol handles the distribution and the **developers** control the access.
Example metadata:
```json
entity_type: track
action: create
signer: "0x885ba66083D1ef52d5Caa8bDFFE7b66f4C21E272"
{
"access_authorities": [
"0xEC8babb083D1ef52d3Cbb8b211E7b66f4C21E242"
],
"data": {
"title": "Latch ft. Sam Smith",
"owner_id": 117827
}
}
```
When a track is uploaded with an access authority, the network will only stream that track if the request is signed by an authorized key.
This creates a flexible architecture where platforms can build custom access rules without modifying the protocol itself.
### How Crate Uses It
In the case of **Friends & Family** and labels using Crate, the platform holds the signing keys that act as access authorities. When artists submit demos:
1. Tracks are uploaded to the Open Audio Protocol
2. Streaming access is restricted to Crate via the authority key
3. A&R teams can listen, review, and collaborate inside the platform
This design provides several advantages:
- Crate doesn't need to operate its own storage or streaming infrastructure
- All submissions live within an open music graph
- Artists maintain a clear path to release if a track is signed
From there, labels and artists have multiple options:
**If a track is archived or saved for later** — Artists can remove the access authority and publish the track directly to Audius or any platform streaming from the protocol.
**If a track is signed** — The label can update the track, re-program and release it directly through the label's address on the Open Audio Protocol or other account as they see fit.
### A New Infrastructure for A&R
Crate represents a new model for how music discovery and signing can work in an era of massive creative output. Instead of fragmented inboxes and private links, A&R teams get a structured pipeline. Instead of siloed platforms, artists publish to an open protocol with programmable rights and distribution. And instead of slow, manual workflows, labels can move at the speed modern music culture demands.
We're incredibly excited to see platforms like Crate emerge. And this is only the beginning of what developers can build on the **Open Audio Protocol**.
## Submit Your Demo
- [Submit a demo to Friends & Family](https://friendsandfamily.crate.is/submit)
- Get in touch with the Crate team: [jesse@audius.co](mailto:jesse@audius.co)
- [Follow Disclosure on Audius](https://audius.co/disclosure)
- [Follow Friends & Family](https://instagram.com/disclosure_friendsandfam)
- [Follow Audius](https://instagram.com/audius)
- [Read the documentation](/tutorials/gate-release-access)
---
> Source: https://docs.audius.co/blog/postmortem-march-2026-consensus-halt
# Postmortem: Network Consensus Halt, March 24, 2026

**Status:** Published
**Date of incident:** March 24, 2026
**Severity:** Critical -- network-wide consensus halt
**Impact:** No new data written to the protocol during the halt, reading data and streaming unimpacted.
## Summary
The Open Audio Protocol experienced a consensus halt starting the morning of March 24, 2026, the first of its kind in the history of the chain. It was detected at around 2am PT at nearly the same time by Figment and Audius teams.
Under the hood, the Open Audio Protocol leverages the [CometBFT](https://github.com/cometbft/cometbft) consensus engine to coordinate audio metadata updates and streaming use in its [core](https://docs.openaudio.org/concepts/validators#core) package. The halt was caused by a cascade of independent failures that collectively dropped the active validator set below the 2/3+ supermajority threshold required. The contributing factors were:
(1) A validator operator with substantial stake and node count changing DNS that silently reduced participation
(2) A separate operator's accidental infrastructure deletion that simultaneously downed many validators before they could be jailed or consensus-deregistered
(3) A handful of validators present in CometBFT's consensus state but missing from `core_validators` application state, due to legacy registration bugs, which complicated normal return to service
Recovery required a database migration to correct the validator set, coordination with multiple staked validator operators to restore availability, and a protocol-level change to accelerate CometBFT round progression to health the network more expediently.
The network fully recovered at 4:47 PM PDT on March 25, 2026.
## Timeline
## Background
### Validator Registration and `core_validators`
The Open Audio Protocol maintains a `core_validators` table in PostgreSQL that tracks all registered validators. Each row stores the validator's public key, endpoint URL, Ethereum address, CometBFT internal address, and service provider ID. This table is the application's source of truth for who is in the validator set.
Validator registration is a multi-step process that bridges Ethereum L1 and the Audio L1:
1. A node operator registers on the Ethereum staking contract.
2. The node's registry bridge detects its own Ethereum registration and submits a registration transaction to the Open Audio chain.
3. Other validators attest to the registration via multi-signature quorum.
4. Once the attestation is finalized in a block, the validator is inserted into `core_validators` and a `ValidatorUpdate` is delivered to CometBFT, adding the node to the consensus set.
Critically, both the `core_validators` insert and the CometBFT `ValidatorUpdate` must happen together. If one succeeds without the other, the application and consensus engine disagree on who is a validator, which can lead to degredation as nodes may not agree on validator set when computing SLA rollups.
### Jailing and the SLA Rollup System
The Open Audio Protocol implements design decisions to favor availability in the effort of allowing modern streamining DSPs to operate close to their web2 counterparts' capabilities.
Validators are expected to actively participate in block production. The protocol tracks this through SLA (Service Level Agreement) rollups:
- At a configured block interval (currently set to 2048 blocks), the block proposer creates an `SlaRollup` transaction covering the recent block range.
- The rollup contains a `SlaNodeReport` for each active validator, recording how many blocks that validator proposed during the interval.
- All validators independently compute the same rollup from their local state. If a proposed rollup doesn't match what a validator computes locally, it rejects the transaction.
A "validator warden" process periodically checks the last 8 SLA rollups for each validator. If a validator proposed zero blocks across all 8 rollups, it is jailed: a `ValidatorDeregistration` transaction (with `Remove = false`) is submitted to consensus, which sets the validator's `jailed` flag to `true` and delivers a power-zero `ValidatorUpdate` to CometBFT. Jailed validators are removed from active consensus but retain their database record and can re-attest to unjail themselves. This allows for CometBFT to maintain a higher block production rate despite having delinquent validators.
This jailing mechanism is relevant to the incident because the SLA rollup validation utilizes the `core_validators` application state to verify validity. Prior to the incident, but undetected, `core_validators` listed 45 validators but CometBFT had 50 in its set. This led to proposals requiring a 34 node supermajority with only visibility into 45 nodes. The ability for a rollup to be validated was therefore reduced.
### Blocks and Rounds in CometBFT Consensus
CometBFT produces blocks through a propose-prevote-precommit cycle. For each block height, a designated proposer creates a block and broadcasts it. Validators then vote in two phases (prevote and precommit). If 2/3+ of voting power agrees, the block is committed. This is a single **round**.
If consensus fails in a round, because the proposer is offline, too few validators respond, or votes don't reach supermajority, the protocol advances to the next round with a different proposer. Each phase has a configurable timeout, and importantly, these timeouts **grow linearly with each round**:
```
timeout = base + (round × delta)
```
The Open Audio Protocol configures these as:
| Phase | Base | Delta |
| --------- | ----- | ----- |
| Propose | 400ms | 75ms |
| Prevote | 300ms | 75ms |
| Precommit | 300ms | 75ms |
In round 0, the full cycle takes roughly 1 second. By round 100, each phase has grown by 7.5 seconds, making a single round take ~23.5 seconds. By round 1000, a single round takes over 3.5 minutes. This linear backoff is designed to give slow nodes time to converge, but it becomes a serious obstacle during recovery from a halt. Nodes must advance through every intervening round sequentially, each one slower than the last.
While per-round timeouts grow linearly, the **cumulative** cost is quadratic. The total time to advance through N rounds is:
```
Σ(r=0..N-1) [1000 + 225r] = 1000N + 225·N(N-1)/2
```
Notably when a node restarts, it resets to round=0 and has to catch up through every intervening round sequentially, each one slower than the last, leading to a several hour max recovery time to a round 1000+ halt, which is what this incident saw.
## Root Causes
### 1. Simultaneous Node State Deletion
A validator operator (theblueprint.xyz) had a disk cleanup routine that unintentionally pruned the CometBFT chain state directory, causing a CometBFT/Postgres state mismatch across their 17 nodes; while blob storage remained fully intact, the nodes became unable to participate in consensus.
### 2. Reduced Validator Participation (DNS Change)
A validator operator (previously known as cultur3stake) changed their DNS configuration to rehome all of their nodes under open-audio-validator.com, causing most of their validators to become unreachable to the rest of the network due to the registration process. Only one of their validators had made it into consensus by the time of the incident (https://val001.open-audio-validator.com/). This silently reduced the effective voting power without good detection. open-audio-validator.com has substantial stake on the network and runs 20 nodes.
### 3. Stale Validators in CometBFT State
Five validators existed in CometBFT's consensus validator set but were missing from the application-level `core_validators` table. These validators were originally registered via a legacy registration path that did not write to `validator_history`. Their `core_validators` rows were lost during a deregistration/re-registration cycle where the DB insert was skipped (duplicate detection) but the CometBFT `ValidatorUpdate` was still delivered.
CometBFT saw 50 validators while `core_validators` listed 45—mostly a mismatch for SLA rollups, artificially inflating the supermajority threshold.
### 4. CometBFT Round Timeout Accumulation
CometBFT increases consensus round timeouts linearly: `timeout = base + (round * delta)`. With default delta values of 75ms, a node that needs to catch up through hundreds of failed rounds accumulates significant delays (e.g., round 1000 x 75ms = 75 seconds per round step). This made recovery painfully slow even after the validator set issues were addressed.
## Impact
- **Full consensus halt** -- no new blocks produced between March 24 1:57 AM PDT and March 25 4:47 PM PDT (38 hours and 50 minutes)
- **Failing writes** -- All downstream services and applications issuing write operations were impacted by the halt.
No impact to reading data or streaming occurred during the halt.
## Patches Deployed
1. **Missing validator migration** (`go-openaudio:v1.2.5`) -- SQL migration to add the 5 stale validators back into `core_validators`, aligning the application-level validator count with CometBFT's consensus state.
2. **Configurable consensus timeout deltas** (branch: `rj-fast-round` via `go-openaudio:5782b161791cdcd800a058d701e02113ae47ec80-amd64`) -- Environment variables (`OPENAUDIO_TIMEOUT_PROPOSE_DELTA`, `OPENAUDIO_TIMEOUT_PREVOTE_DELTA`, `OPENAUDIO_TIMEOUT_PRECOMMIT_DELTA`) to reduce the per-round timeout increment, allowing nodes to catch up through many failed rounds faster during recovery.
Released formally in `go-openaudio:v1.2.6`.
### Prior Related Work (pre-incident)
- **Consensus-based validator deregistration** (March 12, `go-openaudio:v1.2.2`–`go-openaudio:v1.2.4`) -- Replaced direct DB manipulation with consensus transactions to keep CometBFT and application state in sync. This was a preventive fix that addressed the class of bug that created the stale validators, but did not retroactively fix the existing inconsistency.
## Contributing Factors
- **No alerting on validator set divergence** -- The mismatch between CometBFT and `core_validators` was not visible in monitoring until the incident
- **Silent validator dropout** -- DNS changes and node wipes produced no on-chain warnings; the network only failed when it crossed the supermajority threshold
- **Legacy registration path** -- The old registration flow did not maintain consistent state across all tables, creating the conditions for stale validators
- **Narrow effective fault tolerance** -- DNS misconfiguration plus a correlated infrastructure wipe left the network close to the supermajority boundary
## Action Items
| Priority | Action | Status |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| P0 | Deploy cleanup migration for `go-openaudio:v1.2.5` along with round timing configuration, improvements to node registration process, and better /console information | Done (`go-openaudio:v1.2.6`) |
| P1 | Add better monitoring/alerting for CometBFT vs. application validator set divergence | TODO |
| P1 | Add alerting for validator participation drop below safety threshold | TODO |
| P1 | Audit legacy registration paths for other state inconsistencies | TODO |
| P2 | Document validator operator runbook for DNS changes and infrastructure maintenance | TODO |
| P2 | Treat correlated outage exposure as a metric (e.g. validators / voting power sharing operator, DNS zone, or ops boundary)—not only per-operator stake caps | TODO |
| P2 | Continue development of the "proportional rewards" feature that will dispurse additional rewards to node operators that are unjailed and able to complete storage proof challenges | TODO |
## Lessons Learned
1. **State consistency between consensus and application layers is critical.** Any path that modifies one must modify both atomically, or have reconciliation mechanisms. The prior fix to use consensus-based deregistration addressed this going forward, but the pre-existing inconsistency was only caught when it contributed to a halt.
2. **The network's fault tolerance margin was narrow**—especially after correlated loss of many live validators; the small app vs. Comet validator mismatch did not help but was secondary.
3. **CometBFT's linear timeout backoff is hostile to recovery.** After hundreds of missed rounds, the accumulated timeouts make catching up prohibitively slow without intervention.
4. **Validator operator coordination is a first-class operational concern.** DNS changes, infrastructure maintenance, and node resets need to be communicated and staged to avoid compounding failures.
5. **Validator count does not equal true fault tolerance.** Multiple validators run by the same operator, DNS, or on shared infrastructure can still fail together. We need not just stake caps, but also better monitoring, participation alerts, and processes to avoid single points of failure.
6. The network only disincentivizes bad behavior through social slashing. The protocol team is actively developing a feature to dispurse additional rewards to node operators that are unjailed and able to complete storage proof challenges. This will be a key tool for the network to incentivize good behavior and reduce the risk of future consensus halts as well as reliable storage.
7. In future, we recommend that node operators with multiple nodes in their fleet perform maintenance or infrastructure changes in a pharsed or staggered manner to avoid compounding failures and socialize changes.
### Slashing
How slashing works, example `slash` calldata, and links to the staking UI and dashboard are documented under **[Slashing](/concepts/staking#slashing)** on the [Staking](/concepts/staking) page.
## Gratitude
🙏
We are grateful to the following teams for their assistance in investigating and resolving the incident with a high level of urgency, ownership and care for the protocol and its users:
- [theblueprint.xyz](https://theblueprint.xyz/)
- [cultur3stake](https://cultur3.capital/)
- [Figment](https://figment.io/)
- [TikiLabs](https://tikilabs.com/)
- [Altego](https://altego.net)
- Many other indepdent node operators, including but not limited to: Super Cool Labs, audiusindex.org, rickyrombo.com, monophonic.digital, shakespearetech.com, audius-nodes.com