A permissionless music identity and release-consent protocol on Sui, written in Move.
Core defines compositions, recordings, releases, and tracks. Every composition and recording has one canonical share coin: a fixed supply of 100M shares covering all of the work's rights. Composition commission rates, allocations, and royalty accounting belong in extensions.
| Type | Core data |
|---|---|
Composition<CompositionShare> |
Identity and lifecycle; its share coin is the type parameter |
Recording<RecordingShare> |
Identity, lifecycle, and immutable composition ID |
Release |
Identity, lifecycle, and ordered tracks |
Track |
Recording ID and release revenue split |
TrackConsent |
Authorized release ID and nested track; consumed during release creation |
Anyone can create a recording referencing an existing composition. The reference establishes a relationship; it does not establish a license or impose a commission.
Each work's share coin comes from its own copy of a template package in
templates/: composition_share::CompositionShare or
recording_share::RecordingShare. Publish the template's bytecode unchanged; only
the package address differs between works.
- Publish the template.
initsends the publisher a one-timeCreateTicket. create(ticket, &mut CoinRegistry)registers the currency, deletes its metadata cap and returns theTreasuryCap.- In a later transaction,
composition::new(orrecording::new) consumes the treasury cap, mints the full supply to the caller and fixes it.
new aborts unless the currency matches exactly:
| Property | Composition | Recording |
|---|---|---|
| Type | <package>::composition_share::CompositionShare |
<package>::recording_share::RecordingShare |
| Name | Composition Share | Recording Share |
| Symbol | CSHARE |
RSHARE |
| Decimals | 6 | 6 |
| Description, icon URL | empty | empty |
| Supply | 100,000,000, fixed | 100,000,000, fixed |
It also requires the matching treasury cap, zero prior supply, an unregulated currency and locked metadata. Consuming the treasury cap means each share type backs exactly one work.
let (composition, composition_cap, composition_shares) =
composition::new(&mut composition_currency, composition_treasury_cap, ctx);
let (recording, recording_cap, recording_shares) =
recording::new(&composition, &mut recording_currency, recording_treasury_cap, ctx);
composition.publish(&composition_cap);
recording.publish(&recording_cap);
// The caller keeps or transfers the returned admin capabilities and shares.CompositionAdminCap<CompositionShare> and RecordingAdminCap<RecordingShare>
authorize by type: there is exactly one work per share type. Each cap's address is
derived from its work. ReleaseAdminCap stores its release ID and is checked by
release::authorize.
The admin capability represents protocol administration, not fractional economic ownership. Capabilities are transferable bearer objects: transferring one hands over administration. Core has no recovery authority or forced revocation mechanism.
Extensions attach through uid_mut(&admin_cap). The matching capability is required
both before and after publication. Read-only UID access supports extension queries.
Metadata, credits, artwork, audio, and economic agreements such as a composition's commission on its recordings live outside core. Applications requiring economic terms must validate their recognized extension.
Extension modules control their own typed keys and APIs. Mutable UID access is a privileged surface; extension immutability and authorization must be designed explicitly.
Release revenue splits remain part of core consent. A release's ID is derived from
the ordered (recording_id, split) pairs and creator nonce under the shared
ReleaseRegistry. Splits must total 10,000 basis points.
A recording admin calls track::consent(&recording, &cap, release_id, split_bps).
It returns a store-only TrackConsent, which extensions can wrap for offers or
escrow. Its getters expose release_id() and read-only track() access.
release::new accepts vector<TrackConsent>, derives the release ID from the
nested tracks and nonce, and validates and consumes every consent. It stores only
vector<Track>; consent release IDs are discarded. Unwrapping is package-private,
and neither type has copy. There is no public constructor for a bare track.
let consent = track::consent(&recording, &recording_cap, release_id, 10000);
let (release, release_cap) = release::new(&mut registry, vector[consent], nonce);
release.publish(&release_cap);Consent binds membership, ordering, and release splits. Names, artwork, and other extension data are outside that commitment. Publication emits the track events; it does not repeat consent validation.
The registry is created once during package initialization. Release creation is permissionless, but requires the recording-admin-authorized consents.
Core objects are key-only and must be published in their creating transaction. Publication shares the object and freezes its embedded relationships. Authorized extension access remains available afterward.
| Event | Data |
|---|---|
CompositionPublishedEvent<CompositionShare> |
Composition ID |
RecordingPublishedEvent<RecordingShare> |
Recording ID and composition ID |
ReleaseRegistryCreatedEvent |
Registry ID |
ReleaseTrackAssignedEvent |
Release ID, track position, recording ID, split BPS |
ReleasePublishedEvent |
Release ID and creator nonce |
Track events are emitted in tracklist order. Transaction metadata supplies sender and time.
The package follows Sui's Move best practices: constructors return values, publication is separate, capabilities are object-bound, consents are consumed by value, and tests live outside production sources.
Track and TrackConsent share a module to keep consent construction and unwrapping encapsulated. The release registry is shared only by package initialization to preserve its single namespace. Commit publication metadata when this source is deployed.
The only explicit dependency is bps,
used for release track splits. Share coins use Sui's coin_registry. The templates
are test-only dependencies. Sui framework dependencies are pinned in Move.lock.
sui move build
sui move testTests cover the template-to-publish flow, every share currency check, extension access after publication, lifecycle events, release derivation, consent, and split validation.
Apache 2.0 © Miso Labs, Inc.