Cadenza — Technical Specification
On-chain royalty, licensing, and provenance rail. 5 account schemas, 14 instructions, deployed as a Solana Anchor program.
Cadenza — Technical Specification v1
Status: Draft Author: Dennison / Blues Prince Media Date: 2026-04-19 Program: Solana Anchor (Rust) Network: Devnet → Mainnet (Week 0, May 5)
1. What Cadenza Is
Cadenza is the on-chain royalty, licensing, and provenance rail that powers the commerce half of the THIRI × Cadenza ecosystem. It’s a single Solana program (deployed via Anchor) that provides three core operations:
- Mint — Create a music asset with cryptographic provenance
- Split — Automate royalty distribution between collaborators
- License — Grant on-chain usage rights with enforceable terms
Every app in the ecosystem either writes to or reads from Cadenza:
WRITES TO CADENZA READS FROM CADENZA
───────────────── ──────────────────
Mint Studio → CadenzaRecord Royalty Console ← CadenzaRecord
Collab Split → SplitConfig Fan Market ← LicenseGrant
Player THIRI → LicenseGrant Skill Logger ← all accounts
Skill Logger → FingerprintRef Key Finder ← (future) provenance
What Cadenza is NOT:
- Not a token. No SPL token launch, no AMM, no liquidity pool.
- Not a marketplace contract. Fan Market is a separate frontend; Cadenza is the settlement layer beneath it.
- Not a competitor to Metaplex. Cadenza uses Metaplex Core for the NFT standard and adds a music-specific metadata layer on top.
2. Account Architecture
2.1 Program Accounts
All accounts are PDAs (Program Derived Addresses) owned by the Cadenza program.
┌─────────────────────────────────────────────────────────┐
│ CADENZA PROGRAM │
│ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ PublisherNFT │ │ CadenzaRecord │ │
│ │ (1 per dev) │───▶│ (1 per mint) │ │
│ └───────────────┘ └───────┬───────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌────────┐ ┌────────────────┐ │
│ │SplitConfig│ │License │ │FingerprintRef │ │
│ │(per collab)│ │Grant │ │(per user) │ │
│ └──────────┘ └────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────┘
2.2 Account Schemas (Anchor IDL)
PublisherNFT
The cryptographic developer identity. Minted once during the Mainnet Foundation Sprint. Every subsequent app and mint links back to this.
#[account]
pub struct PublisherNft {
/// The wallet that owns this publisher identity
pub authority: Pubkey, // 32 bytes
/// Human-readable publisher name
pub name: String, // 4 + 64 bytes max
/// URI to off-chain metadata (logo, bio, links)
pub metadata_uri: String, // 4 + 200 bytes max
/// Number of CadenzaRecords minted under this publisher
pub mint_count: u64, // 8 bytes
/// Timestamp of creation
pub created_at: i64, // 8 bytes
/// Bump seed for PDA derivation
pub bump: u8, // 1 byte
}
// PDA seeds: ["publisher", authority.key()]
// Total: ~317 bytes + discriminator
CadenzaRecord
Attached to every music NFT minted through Mint Studio. This is the music-specific metadata that Metaplex Core doesn’t provide.
#[account]
pub struct CadenzaRecord {
/// The publisher who minted this
pub publisher: Pubkey, // 32 bytes
/// The Metaplex Core asset address
pub asset_mint: Pubkey, // 32 bytes
/// Original creator wallet(s) — up to 5
pub creators: Vec<Pubkey>, // 4 + (32 × 5) = 164 bytes max
/// Music-specific metadata
pub title: String, // 4 + 100 bytes max
pub key_signature: Option<String>,// 1 + 4 + 12 bytes (e.g. "Bb minor")
pub tempo_bpm: Option<u16>, // 1 + 2 bytes
pub duration_secs: Option<u32>, // 1 + 4 bytes
pub genre: Option<String>, // 1 + 4 + 32 bytes max
/// WoodShed fingerprint hash (SHA-256 of harmonic analysis)
pub fingerprint_hash: Option<[u8; 32]>, // 1 + 32 bytes
/// Off-chain audio URI (Arweave / IPFS)
pub audio_uri: String, // 4 + 200 bytes max
/// Off-chain artwork URI
pub artwork_uri: String, // 4 + 200 bytes max
/// Licensing terms
pub license_type: LicenseType, // 1 byte (enum)
/// Whether secondary sales are allowed
pub secondary_allowed: bool, // 1 byte
/// Royalty basis points for secondary sales (e.g. 500 = 5%)
pub royalty_bps: u16, // 2 bytes
/// Active split config (if collaborators exist)
pub split_config: Option<Pubkey>, // 1 + 32 bytes
/// Timestamps
pub created_at: i64, // 8 bytes
pub updated_at: i64, // 8 bytes
/// Bump
pub bump: u8, // 1 byte
}
// PDA seeds: ["cadenza_record", asset_mint.key()]
// Total: ~893 bytes max + discriminator
SplitConfig
Defines how royalties are distributed between collaborators. Created by Collab Split, referenced by Cadenza Record.
#[account]
pub struct SplitConfig {
/// The CadenzaRecord this split applies to
pub cadenza_record: Pubkey, // 32 bytes
/// Who can modify this split (typically the primary creator)
pub authority: Pubkey, // 32 bytes
/// Splits — up to 10 recipients
pub splits: Vec<Split>, // 4 + (40 × 10) = 404 bytes max
/// Whether the split is locked (immutable after lock)
pub locked: bool, // 1 byte
/// Timestamps
pub created_at: i64, // 8 bytes
pub locked_at: Option<i64>, // 1 + 8 bytes
/// Bump
pub bump: u8, // 1 byte
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct Split {
/// Recipient wallet
pub recipient: Pubkey, // 32 bytes
/// Basis points (e.g. 5000 = 50%)
pub share_bps: u16, // 2 bytes
/// Role description (e.g. "producer", "vocalist")
pub role: String, // 4 + 2 bytes (index into role enum)
}
// PDA seeds: ["split", cadenza_record.key()]
LicenseGrant
On-chain record that wallet X has a license to use asset Y under specific terms. Created when a license is purchased through Fan Market or Player THIRI.
#[account]
pub struct LicenseGrant {
/// The CadenzaRecord being licensed
pub cadenza_record: Pubkey, // 32 bytes
/// The wallet receiving the license
pub licensee: Pubkey, // 32 bytes
/// The wallet that granted the license
pub licensor: Pubkey, // 32 bytes
/// License terms
pub license_type: LicenseType, // 1 byte
/// Usage scope
pub usage: UsageScope, // 1 byte (enum)
/// Duration (None = perpetual)
pub expires_at: Option<i64>, // 1 + 8 bytes
/// Price paid in lamports
pub price_lamports: u64, // 8 bytes
/// Whether license is active
pub active: bool, // 1 byte
/// Timestamps
pub created_at: i64, // 8 bytes
pub revoked_at: Option<i64>, // 1 + 8 bytes
/// Bump
pub bump: u8, // 1 byte
}
// PDA seeds: ["license", cadenza_record.key(), licensee.key()]
FingerprintRef
Links a user’s WoodShed fingerprint to their on-chain identity. Written by Skill Logger.
#[account]
pub struct FingerprintRef {
/// The wallet this fingerprint belongs to
pub owner: Pubkey, // 32 bytes
/// SHA-256 hash of the latest WoodShed fingerprint
pub fingerprint_hash: [u8; 32], // 32 bytes
/// Number of analysis sessions that contributed
pub session_count: u64, // 8 bytes
/// Off-chain URI to the full fingerprint data
pub data_uri: String, // 4 + 200 bytes max
/// Timestamps
pub created_at: i64, // 8 bytes
pub updated_at: i64, // 8 bytes
/// Bump
pub bump: u8, // 1 byte
}
// PDA seeds: ["fingerprint", owner.key()]
2.3 Enums
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
pub enum LicenseType {
/// All rights reserved — no licensing without explicit grant
AllRightsReserved,
/// Creative Commons Attribution
CcBy,
/// Creative Commons Attribution-ShareAlike
CcBySa,
/// Creative Commons Attribution-NonCommercial
CcByNc,
/// Sync license — for video/film/content use
Sync,
/// Sample license — can be chopped/remixed
Sample,
/// Custom — terms defined off-chain
Custom,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
pub enum UsageScope {
/// Personal / non-commercial use
Personal,
/// Commercial use (streaming, content creation)
Commercial,
/// Derivative works (remixes, samples)
Derivative,
/// Synchronization (video, film, ads)
Synchronization,
/// Unlimited — all usage rights
Unlimited,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
pub enum CreatorRole {
Producer,
Vocalist,
Songwriter,
Instrumentalist,
Engineer,
Arranger,
Lyricist,
Other,
}
3. Instructions (Program API)
3.1 Publisher Operations
| Instruction | Signer | Description |
|---|---|---|
initialize_publisher | authority | Create PublisherNFT PDA; one-time setup |
update_publisher | authority | Update name, metadata_uri |
3.2 Mint Operations (Mint Studio)
| Instruction | Signer | Description |
|---|---|---|
mint_cadenza_record | publisher.authority | Create Metaplex Core asset + CadenzaRecord PDA. Requires PublisherNFT. |
update_cadenza_record | publisher.authority | Update mutable fields (metadata, URIs, license type) |
burn_cadenza_record | publisher.authority | Burn the asset and close the CadenzaRecord account |
mint_cadenza_record flow:
1. Verify signer holds PublisherNFT
2. Create Metaplex Core asset (CPI to mpl_core)
3. Initialize CadenzaRecord PDA with music metadata
4. Increment publisher.mint_count
5. Emit MintEvent { asset_mint, publisher, title, timestamp }
3.3 Split Operations (Collab Split)
| Instruction | Signer | Description |
|---|---|---|
create_split | cadenza_record.publisher.authority | Create SplitConfig for a CadenzaRecord. Splits must sum to 10000 bps. |
update_split | split.authority | Modify split percentages (only while unlocked) |
lock_split | split.authority | Permanently freeze the split config |
distribute_split | any (permissionless crank) | Disburse SOL held in escrow according to split percentages |
distribute_split flow:
1. Read SplitConfig.splits[]
2. Calculate each recipient's share of escrow balance
3. Transfer SOL to each recipient wallet
4. Emit DistributeEvent { cadenza_record, amounts[], recipients[], timestamp }
3.4 License Operations (Fan Market / Player THIRI)
| Instruction | Signer | Description |
|---|---|---|
grant_license | licensor (asset owner) | Create LicenseGrant PDA; transfer payment to licensor (or escrow → split) |
revoke_license | licensor | Deactivate a LicenseGrant |
verify_license | any (read-only) | Check if a wallet holds an active license for an asset |
grant_license flow:
1. Verify licensor owns the CadenzaRecord's asset
2. Transfer price_lamports from licensee → escrow
3. If SplitConfig exists: distribute via split percentages
4. Else: transfer full amount to licensor
5. Create LicenseGrant PDA
6. Emit LicenseEvent { cadenza_record, licensee, license_type, price, timestamp }
3.5 Fingerprint Operations (Skill Logger)
| Instruction | Signer | Description |
|---|---|---|
register_fingerprint | owner | Create or update FingerprintRef PDA |
increment_sessions | owner | Bump session_count; called after each THIRI app interaction |
4. How Each App Connects to Cadenza
4.1 App → Instruction Mapping
| App | Instructions Used | Data Flow |
|---|---|---|
| Mainnet Foundation | initialize_publisher | Creates the PublisherNFT; one-time bootstrap |
| Mint Studio | mint_cadenza_record, update_cadenza_record | Artist creates music NFTs; all metadata written to CadenzaRecord |
| Royalty Console | — (read-only) | Reads CadenzaRecords + SplitConfigs to display royalty dashboard |
| Collab Split | create_split, update_split, lock_split, distribute_split | Manages multi-wallet royalty splits |
| Stem Splitter | — (indirect) | Stems could be minted as derivative CadenzaRecords (future) |
| Key Finder | — (indirect) | Detected key feeds CadenzaRecord.key_signature at mint time |
| Chord Analyzer | — (indirect) | Chord data feeds CadenzaRecord.fingerprint_hash |
| Ear Trainer | increment_sessions | Each drill session bumps the user’s FingerprintRef |
| Player THIRI | grant_license, verify_license | Playback checks license; provenance mint on remix |
| Key Transposer | — (read-only) | Reads CadenzaRecord.key_signature for context |
| Setlist Planner | — (read-only) | Reads tempo, key, duration from CadenzaRecords |
| Fan Market | grant_license, revoke_license | Two-sided marketplace; license purchase triggers split distribution |
| Skill Logger | register_fingerprint, increment_sessions | Aggregates cross-app usage into on-chain identity |
4.2 WoodShed ↔ Cadenza Bridge
The WoodShed engine exports types that map directly to Cadenza on-chain fields:
| WoodShed Type | Cadenza Field | Transformation |
|---|---|---|
ChordData.symbol | CadenzaRecord.key_signature | Key Finder resolves root + quality → key string |
MIDIData.tempo | CadenzaRecord.tempo_bpm | Direct mapping (u16) |
Voicing.midiNotes[] | FingerprintRef.fingerprint_hash | SHA-256 of accumulated voicing choices |
StyleConfig.name | CadenzaRecord.genre | Style name → genre string |
ReharmonizedProgression | CadenzaRecord.fingerprint_hash | Reharmonization patterns feed the fingerprint |
5. Security Model
5.1 Access Control
| Account | Who Can Write | Who Can Read |
|---|---|---|
PublisherNFT | authority only | anyone |
CadenzaRecord | publisher.authority only | anyone |
SplitConfig | split.authority (until locked) | anyone |
LicenseGrant | licensor (create/revoke) | anyone (verify) |
FingerprintRef | owner only | anyone |
5.2 Invariants (enforced by the program)
- Split percentages must sum to exactly 10,000 bps — any other value is rejected
- Locked splits are immutable — once
lock_splitis called, no further updates - One PublisherNFT per wallet — PDA derivation enforces uniqueness
- One FingerprintRef per wallet — same mechanism
- License grants are unique per (asset, licensee) pair — PDA seeds enforce this
- Only asset owners can license — verified via Metaplex Core ownership check
- Payments are atomic — SOL transfer + PDA creation in one transaction
5.3 Rent Exemption
All accounts are rent-exempt (Solana standard since 1.15). Account sizes:
| Account | Max Size | Rent (SOL) |
|---|---|---|
| PublisherNFT | ~325 bytes | ~0.0032 |
| CadenzaRecord | ~900 bytes | ~0.0082 |
| SplitConfig | ~490 bytes | ~0.0046 |
| LicenseGrant | ~145 bytes | ~0.0018 |
| FingerprintRef | ~295 bytes | ~0.0030 |
6. Events
All instructions emit CPI events for indexing by off-chain services (Helius, SimpleHash, custom indexer).
#[event]
pub struct MintEvent {
pub asset_mint: Pubkey,
pub publisher: Pubkey,
pub title: String,
pub timestamp: i64,
}
#[event]
pub struct DistributeEvent {
pub cadenza_record: Pubkey,
pub total_lamports: u64,
pub recipients: Vec<Pubkey>,
pub amounts: Vec<u64>,
pub timestamp: i64,
}
#[event]
pub struct LicenseEvent {
pub cadenza_record: Pubkey,
pub licensee: Pubkey,
pub licensor: Pubkey,
pub license_type: LicenseType,
pub price_lamports: u64,
pub timestamp: i64,
}
#[event]
pub struct FingerprintEvent {
pub owner: Pubkey,
pub fingerprint_hash: [u8; 32],
pub session_count: u64,
pub timestamp: i64,
}
7. Client SDK (TypeScript)
The Cadenza TypeScript SDK wraps the Anchor IDL for frontend consumption. Ships as @bluesprince/cadenza-sdk alongside @bluesprince/woodshed-engine.
// @bluesprince/cadenza-sdk — public API surface
// ── Connection ──────────────────────────────────────────────
export function createCadenzaClient(
connection: Connection,
wallet: AnchorWallet,
): CadenzaClient;
// ── Publisher ───────────────────────────────────────────────
export async function initializePublisher(
client: CadenzaClient,
name: string,
metadataUri: string,
): Promise<{ publisherPda: PublicKey; tx: string }>;
// ── Minting ─────────────────────────────────────────────────
export async function mintCadenzaRecord(
client: CadenzaClient,
params: {
title: string;
audioUri: string;
artworkUri: string;
keySignature?: string;
tempoBpm?: number;
durationSecs?: number;
genre?: string;
fingerprintHash?: Uint8Array;
licenseType: LicenseType;
royaltyBps: number;
},
): Promise<{ assetMint: PublicKey; recordPda: PublicKey; tx: string }>;
// ── Splits ──────────────────────────────────────────────────
export async function createSplit(
client: CadenzaClient,
cadenzaRecord: PublicKey,
splits: Array<{ recipient: PublicKey; shareBps: number; role: CreatorRole }>,
): Promise<{ splitPda: PublicKey; tx: string }>;
export async function distributeSplit(
client: CadenzaClient,
splitConfig: PublicKey,
): Promise<{ tx: string; amounts: Record<string, number> }>;
export async function lockSplit(
client: CadenzaClient,
splitConfig: PublicKey,
): Promise<{ tx: string }>;
// ── Licensing ───────────────────────────────────────────────
export async function grantLicense(
client: CadenzaClient,
params: {
cadenzaRecord: PublicKey;
licensee: PublicKey;
licenseType: LicenseType;
usage: UsageScope;
priceLamports: number;
expiresAt?: number;
},
): Promise<{ licensePda: PublicKey; tx: string }>;
export async function verifyLicense(
client: CadenzaClient,
cadenzaRecord: PublicKey,
licensee: PublicKey,
): Promise<{ active: boolean; grant: LicenseGrant | null }>;
// ── Fingerprint ─────────────────────────────────────────────
export async function registerFingerprint(
client: CadenzaClient,
fingerprintHash: Uint8Array,
dataUri: string,
): Promise<{ fingerprintPda: PublicKey; tx: string }>;
// ── Queries ─────────────────────────────────────────────────
export async function getPublisher(
client: CadenzaClient,
authority: PublicKey,
): Promise<PublisherNft | null>;
export async function getRecordsByPublisher(
client: CadenzaClient,
publisher: PublicKey,
): Promise<CadenzaRecord[]>;
export async function getLicensesByWallet(
client: CadenzaClient,
wallet: PublicKey,
): Promise<LicenseGrant[]>;
8. Fee Structure
| Operation | Fee | Recipient |
|---|---|---|
initialize_publisher | Free (rent only) | — |
mint_cadenza_record | 0.01 SOL | BPM treasury |
create_split | Free (rent only) | — |
distribute_split | 1% of disbursement | BPM treasury |
grant_license | 2.5% of price | BPM treasury |
register_fingerprint | Free (rent only) | — |
Treasury wallet: BPM... (to be generated during Mainnet Foundation Sprint)
9. Deployment Plan
Phase 0: Devnet (Week -1, Apr 28 – May 4)
# Install toolchain
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
cargo install --avm anchor-cli
# Initialize project
cd commerce/cadenza
anchor init cadenza-program --javascript
# Deploy to devnet
anchor build
anchor deploy --provider.cluster devnet
Phase 1: Mainnet (Week 0, May 5)
# Switch to mainnet
anchor deploy --provider.cluster mainnet-beta
# Initialize PublisherNFT
npx ts-node scripts/init-publisher.ts \
--name "Blues Prince Media" \
--metadata-uri "https://thiri.ai/publisher.json"
Phase 2: Client SDK (Week 0-1)
# Generate TypeScript types from IDL
anchor build # generates target/idl/cadenza.json
npx @coral-xyz/anchor gen-ts --idl target/idl/cadenza.json
# Publish SDK
cd sdk
npm publish --access public
10. Testing Strategy
Unit Tests (Anchor)
tests/
├── publisher.test.ts — init, update, uniqueness constraint
├── mint.test.ts — mint, update, burn, publisher verification
├── split.test.ts — create, update, lock, invariant (sum = 10000)
├── license.test.ts — grant, revoke, verify, payment flow
├── fingerprint.test.ts — register, increment, uniqueness
└── integration.test.ts — full flow: publish → mint → split → license → distribute
Key Test Cases
- Split invariant: reject splits that don’t sum to 10,000 bps
- Lock immutability: reject updates after lock_split
- License uniqueness: reject duplicate license for same (asset, wallet)
- Payment atomicity: verify SOL transfers match split percentages
- Publisher uniqueness: reject second PublisherNFT for same wallet
- Ownership verification: reject license grants from non-owners
- Rent exemption: verify all accounts satisfy minimum rent
11. Open Questions
[!IMPORTANT] These need decisions before Week 0 deploy.
-
Metaplex Core vs. Token-2022? — Core is newer, simpler, cheaper. Token-2022 has wider wallet support. Recommendation: Core (Seeker wallets support it; simpler account model).
-
Arweave vs. IPFS for audio storage? — Arweave is permanent but costs ~$0.10/MB. IPFS is free but needs pinning. Recommendation: Arweave for masters, IPFS for previews.
-
Should SplitConfig support time-based splits? (e.g., Producer gets 80% first 90 days, then 50% forever). Not in v1 — adds complexity. Can add
SplitScheduleaccount later. -
Treasury multisig or single wallet? — Multisig (Squads Protocol) is safer but adds deployment complexity. Recommendation: Start single, migrate to Squads at >$10K monthly volume.
-
Should Key Finder / Chord Analyzer auto-populate CadenzaRecord fields? — Yes, but only at mint time (not retroactively). WoodShed analysis results are passed as instruction args to
mint_cadenza_record.
12. Relationship to Existing THIRI API
The existing commerce/thiri-api handles off-chain license management (Stripe checkout, Supabase license keys) for the THIRI VST plugins. Cadenza handles on-chain licensing for the Seeker dApp ecosystem.
They coexist:
| THIRI API | Cadenza | |
|---|---|---|
| What it licenses | VST plugins (Voice, Keys, Seq) | Music assets (recordings, stems, compositions) |
| Payment rail | Stripe (fiat) | Solana (SOL) |
| Storage | Supabase | Solana accounts |
| Target | Desktop producers | Mobile Seeker users |
| Verification | API call to /api/verify | On-chain PDA lookup |
Future bridge: When a THIRI Pro user mints a track produced in the VST, the THIRI API verifies their license → Cadenza mints the CadenzaRecord. This connects the desktop production workflow to the mobile distribution workflow.
Last updated: 2026-04-19 · Blues Prince Media