🎵 Music & Sound Design · instrumental sample rights

Instrument Sample Provenance

Create NFTs proving ownership of original instrument sample recordings.

License Terms + Hook· programmable rights
Section · Onchain

The primitive.

full primer →

Musicians attach programmable License Terms — remix, commercial-use, royalty — the moment the instrumental sample rights is registered, and a DATA Foundation licensing hook enforces the rules onchain instead of in DMs.

Why this primitivethe terms of remix / commercial-use / royalty need to enforce themselves in code, not in DMs

Kernel
PIL License Terms attached at registration through `licenseTermsData`, optionally wired to a whitelisted DATA Foundation licensing hook (LockLicenseHook, TotalLicenseTokenLimitHook) so remix / commercial-use / royalty rules live onchain and enforce themselves
Drives the UI as
a rights sheet that renders the terms + hook in plain language and shows the licenseTermsId beside the IP Asset
Appendix · Secrets

Required keys.

PRIVY_APP_ID
Publishable — hardcode into src/config.ts. Google sign-in + Aeneid embedded wallet + sponsored tx.
open ↗
AENEID_RPC_URL
Publishable — hardcode into src/config.ts. Default: https://aeneid.storyrpc.io
open ↗

Hardcode both into src/config.ts (they're publishable, not secrets) before pasting the prompt below.

Appendix · Mega-prompt

The build prompt.

Paste into a fresh Lovable project. Make sure src/config.ts holds both values above. read the build strategy →

Build "Instrument Sample Provenance" in ONE Lovable message. Single-page demo on the DATA Foundation (Aeneid testnet).

CONCEPT
Create NFTs proving ownership of original instrument sample recordings.
Discipline: Music & Sound Design (instrumental sample rights).
On-chain primitive: License Terms + Hook (programmable rights). Why this primitive: the terms of remix / commercial-use / royalty need to enforce themselves in code, not in DMs
ipType: Song

5-CREDIT BUDGET (HARD LIMIT):
- ONE single-page app. No router, no Lovable Cloud, no database, no auth flows beyond Privy drop-in.
- Bespoke Solidity IS allowed but must stay tiny and sandbox-deployable: at most 1 contract file, ~120 LOC, no external deps beyond OpenZeppelin, no proxies, no upgradeability. Deploy it from the Lovable sandbox with Foundry (`forge script ... --rpc-url https://aeneid.storyrpc.io --broadcast --private-key $DEPLOYER_PK`) — never from the browser and never as part of the app runtime. Commit the resulting `{ address, chainId: 1315, abi }` into `src/data/<contract>.json` and import from there. For anything the SDKs already cover (register IP, license terms, mint license, CDR), keep using @story-protocol/core-sdk / @piplabs/cdr-sdk — do not re-implement those primitives in Solidity. Never redeploy Aeneid core contracts — they are already live.
- Privy is always the auth + sponsored-tx layer (Google login, embedded wallet).
- Off-chain storage: use IPFS only if the artefact must be public; use CDR (@piplabs/cdr-sdk) if it must be private.
- At most ONE AI call per user action (use Lovable AI Gateway with LOVABLE_API_KEY if AI is part of the idea).
- Skip tests, skip CI, skip docs pages. Ship the demo, nothing else.

STACK
- React + Vite single page (the index route).
- Chain: DATA Foundation Aeneid testnet. chainId: 1315. Explorer: https://aeneid.explorer.datafdn.org  Block explorer: https://aeneid.datanetscan.io  Faucet: https://aeneid.faucet.datafdn.org
- SDKs (install with `bun add`):
    @story-protocol/core-sdk   // IP Assets, License Terms, License Tokens, Royalties
    @piplabs/cdr-sdk           // Confidential Data Rails (encrypt / gate / decrypt) — LAZY-LOAD only
    @privy-io/react-auth       // Google sign-in + embedded wallet + sponsored tx
    viem                        // walletClient wrapper around Privy embedded provider
- Custom contract path (optional — use only when the idea needs a bespoke event
  / registry / counter the SDKs don't cover; the demo runtime must still be a
  pure React + Vite single page):
    contracts/                     # foundry project committed alongside src/
      foundry.toml
      src/<Name>.sol               // your ~120 LOC contract, OpenZeppelin OK
      script/Deploy.s.sol          // forge script that broadcasts to Aeneid
  Deploy step (runs inside the Lovable sandbox — no local toolchain needed):
    cd contracts && forge build && \
      forge script script/Deploy.s.sol \
        --rpc-url https://aeneid.storyrpc.io \
        --broadcast --private-key $DEPLOYER_PK
  Fund the deployer once at https://aeneid.faucet.datafdn.org. Store
  DEPLOYER_PK as a Lovable Project Secret; never commit it. After deploy,
  write `{ address, chainId: 1315, abi }` to `src/data/<name>.json` and read
  it from the single-page app via viem `readContract` / `writeContract`.
  Reference: `contracts/ChoreoLedger.sol` + `contracts/script/Deploy.s.sol`
  + `src/data/choreo-ledger.json` in this repo.
- SSR-safe Privy mount is mandatory. Never import @privy-io/react-auth at
  module scope of a route file — it crashes SSR. Use
  lazy(() => import('./privy-client-entry')) inside <ClientOnly> + <Suspense>,
  and put <PrivyProvider> only inside privy-client-entry.tsx.
- SSR-safe CDR mount is mandatory. Never top-level-import @piplabs/cdr-sdk.
  Do `const cdr = await import('@piplabs/cdr-sdk')` inside the handler that
  actually needs to encrypt or decrypt. The bundled WASM must load on the
  client, not during SSR (breaks the published bundle otherwise).
- PrivyProvider config (do NOT stub defaultChain — chainId 1315 is passed per-call):
    import { PRIVY_APP_ID } from '@/config';
    <PrivyProvider appId={PRIVY_APP_ID}
      config={{ loginMethods:['google','email'],
                embeddedWallets:{ ethereum:{ createOnLogin:'users-without-wallets' } },
                appearance:{ theme:'dark' } }}>
- Read the embedded wallet from useWallets, not user.wallet:
    const embedded = wallets.find(w => w.walletClientType === 'privy');
- Bridge the Privy embedded wallet into a viem walletClient for the Story SDK:
    import { AENEID_RPC_URL } from '@/config';
    const provider = await embedded.getEthereumProvider();
    const walletClient = createWalletClient({
      account: embedded.address as `0x${string}`,
      chain: { id: 1315, name: 'Story Aeneid', nativeCurrency: { name:'IP', symbol:'IP', decimals:18 },
               rpcUrls:{ default:{ http:[AENEID_RPC_URL] } } },
      transport: custom(provider),
    });
- Instantiate the Story client (SDK auto-wires Aeneid contracts):
    const story = StoryClient.newClient({
      wallet: walletClient,
      transport: http(AENEID_RPC_URL),
      chainId: 'aeneid',
    });
- Every raw sendTransaction (rare — mostly the SDK handles it) goes through
  Privy `useSendTransaction` with BOTH `address` and `sponsor`, ALWAYS with
  an explicit gas fallback (Privy embedded smart wallets reject gas: 0), and
  wrapped in a 45s Promise.race timeout:
    const gas = await publicClient.estimateGas({ to, data, account: embedded.address })
      .then(g => g * 120n / 100n).catch(() => 300_000n);
    await Promise.race([
      sendTransaction(
        { to, data, chainId: 1315, gas },
        { address: embedded.address, sponsor: true }
      ),
      new Promise((_, r) => setTimeout(() => r(new Error(
        "Privy sendTransaction timed out after 45s. Enable Gas sponsorship -> App pays -> Story Aeneid -> Allow transactions from the client. If Aeneid sponsorship is unavailable in your workspace, drop `sponsor: true` and prefund the embedded wallet from https://aeneid.faucet.datafdn.org"
      )), 45_000)),
    ]);
- Do NOT pass uiOptions:{ showWalletUIs:false } — it aborts with
  "signal is aborted without reason". The approval sheet still shows on
  the embedded-EOA path; the fee reads US$0.00.
- Do NOT add ZeroDev / SmartWalletsProvider / a paymaster URL. Native
  Privy sponsorship on Aeneid works with the toggles below and nothing else.
- DASHBOARD PREREQUISITE (one-time): Privy dashboard -> Gas sponsorship
  -> App pays -> add "Story Aeneid" (chainId 1315) -> toggle "Allow
  transactions from the client" ON. Without this, sendTransaction hangs silently.
  Fallback: if your Privy workspace does not offer Aeneid sponsorship yet,
  drop `sponsor: true`, tell the user to hit the faucet
  (https://aeneid.faucet.datafdn.org) once, then re-run — the fallback branch
  in the timeout wrapper already handles this.

ON-CHAIN WRITE (instrumental sample rights artefact -> IP Asset + PIL License Terms + optional hook):
    const ipMetadata = story.ipAsset.generateIpMetadata({
      title: "Instrument Sample Provenance",
      description: "Create NFTs proving ownership of original instrument sample recordings.",
      createdAt: Math.floor(Date.now() / 1000).toString(),
      ipType: "Song",
      mediaUrl,
      mediaHash,         // sha256(await file.arrayBuffer()) as 0x-prefixed hex
      mediaType: file.type,
      image: previewUrl,
      imageHash: mediaHash,
      creators: [{ name: displayName, address: embedded.address, contributionPercent: 100 }],
      tags: ["instrumental sample rights", "Song"],
    });
    const { ipId, licenseTermsIds, txHash } = await story.ipAsset.registerIpAsset({
      nft: { type: 'mint', spgNftContract: '0xc32A8a0FF3beDDDa58393d022aF433e78739FAbc' },
      ipMetadata,
      licenseTermsData: [{
        terms: {
          transferable: true,
          commercialUse: true,
          commercialRevShare: 5,          // 5% of downstream revenue back to the licensor
          derivativesAllowed: true,
          defaultMintingFee: 0n,
          currency: '0x1514000000000000000000000000000000000000', // WIP on Aeneid
        },
        licensingConfig: {
          isSet: true,
          mintingFee: 0n,
          // Optional: cap total license mints. Whitelisted hook address from
          //   https://docs.datafdn.org/developers/deployed-smart-contracts
          licensingHook: '0xaBAD364Bfa41230272b08f171E0Ca939bD600478', // TotalLicenseTokenLimitHook
          hookData: '0x',
          commercialRevShare: 5,
          disabled: false,
          expectMinimumGroupRewardShare: 0,
          expectGroupRewardPool: '0x0000000000000000000000000000000000000000',
        },
      }],
    });
    // If using TotalLicenseTokenLimitHook, cap the mints:
    await story.license.setMaxLicenseTokens({ ipId, licenseTermsId: licenseTermsIds[0], maxLicenseTokens: 1000 });

USER FLOW
1. Land on page -> 'Sign in with Google' (Privy) -> embedded wallet auto-provisioned on Aeneid.
2. Show a rights sheet: 'commercial use ✓ · 5% royalty · remixable · capped at 1000 licenses'. Render the licenseTermsId beside the IP Asset link. Explain in one plain-language line what the terms mean for a downstream instrumental sample rights creator.
3. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14"

CONFIG (hardcode these two publishable values; they are NOT secrets):
- Privy App ID          Google sign-in + Aeneid embedded wallet. Hardcode into src/config.ts as
                        `export const PRIVY_APP_ID = "<your-app-id>"`. Get it at https://dashboard.privy.io
                        Docs: https://docs.privy.io/llms-full.txt
- Aeneid RPC URL        DATA Foundation Aeneid HTTPS endpoint. Hardcode into src/config.ts as
                        `export const AENEID_RPC_URL = "https://aeneid.storyrpc.io"` (default works;
                        swap in your own provider under load).

Lovable's `VITE_*` prefix is reserved by the platform, so client-side keys go in
committed config files. Both values above are publishable — they show up in the
built JS bundle and are safe to commit.

OPTIONAL SECRET (Lovable -> Project Settings -> Secrets):
- LOVABLE_API_KEY       Only if the idea makes an AI call. Auto-provisioned when
                        you turn on Lovable AI.

CREDIT (must appear in UI footer AND inside every IP Asset's `description` or `tags`):
Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
Appendix · Market

Market sizing.

TAM
$1.5B
instrument sample libraries
SAM
$400M
instrument sampling services
SOM
$70M
boutique instrument sample sellers

Indicative figures for hackathon pitches — refine with your own research before raising.

See also

Adjacent entries.