👗 Fashion & Textile Design · yarn and thread digital catalogs

Thread Token

Pin yarn samples and thread color specs for easy supplier access.

CDR encrypted vault· confidential data
Section · Onchain

The primitive.

full primer →

The yarn and thread digital catalogs is sealed in a Confidential Data Rails vault against the validator DKG key; only fashion designers holding the gating License Token can trigger threshold decryption and reconstruct the artefact client-side.

Why this primitivethe underlying artefact is sensitive and must stay encrypted at rest but programmatically unlockable

Kernel
Confidential Data Rails via @piplabs/cdr-sdk — an Uploader allocates a vault, TDH2-encrypts the artefact against the DKG public key with `uuidToLabel(uuid)` binding, and the on-chain condition gates threshold decryption on holding the License Token minted above
Drives the UI as
a 'sealed on Aeneid CDR' chip with the vault uuid; consumers with the License Token see a 'decrypt' action that stitches partials client-side
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 "Thread Token" in ONE Lovable message. Single-page demo on the DATA Foundation (Aeneid testnet).

CONCEPT
Pin yarn samples and thread color specs for easy supplier access.
Discipline: Fashion & Textile Design (yarn and thread digital catalogs).
On-chain primitive: CDR encrypted vault (confidential data). Why this primitive: the underlying artefact is sensitive and must stay encrypted at rest but programmatically unlockable
ipType: Garment

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 (yarn and thread digital catalogs artefact -> encrypted CDR vault gated by a License Token):
    // Lazy-load the CDR SDK inside the handler (top-level import breaks SSR + bundle):
    const cdr = await import('@piplabs/cdr-sdk');
    const cdrClient = cdr.CDRClient.forChain('aeneid', { walletClient });
    // 1. Allocate a vault whose read condition is "holder of License Token X":
    const vault = await cdrClient.uploader.allocate({
      readCondition: cdr.conditions.holdsLicenseToken({ ipId, licenseTermsId }),
    });
    // 2. Encrypt the artefact bytes; the label MUST equal uuidToLabel(vault.uuid):
    const ciphertext = await cdrClient.uploader.encrypt({
      uuid: vault.uuid,
      plaintext: new Uint8Array(await file.arrayBuffer()),
    });
    // 3. Write ciphertext + condition to CDR; validators enforce threshold decryption:
    const { txHash } = await cdrClient.uploader.write({ vault, ciphertext });

    // ALSO register a public-metadata-only IP Asset so the vault is discoverable:
    const ipMetadata = story.ipAsset.generateIpMetadata({
      title: "Thread Token",
      description: "Pin yarn samples and thread color specs for easy supplier access.",
      createdAt: Math.floor(Date.now() / 1000).toString(),
      ipType: "Garment",
      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: ["yarn and thread digital catalogs", "Garment"],
    });
    const { ipId } = await story.ipAsset.registerIpAsset({
      nft: { type: 'mint', spgNftContract: '0xc32A8a0FF3beDDDa58393d022aF433e78739FAbc' },
      ipMetadata: { ...ipMetadata, cdrVaultUuid: vault.uuid, mediaUrl: undefined, mediaHash: undefined },
    });

USER FLOW
1. Land on page -> 'Sign in with Google' (Privy) -> embedded wallet auto-provisioned on Aeneid.
2. Show a 'sealed on Aeneid CDR' chip with the vault uuid. Consumers who hold the gating License Token see a 'decrypt' action that calls `cdrClient.consumer.collectPartials(vault.uuid)` then `cdrClient.consumer.decrypt()` — the plaintext is reconstructed client-side and never touches your server.
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
$700M
yarn and thread supply chain
SAM
$160M
textile supplier software
SOM
$12M
digital thread catalogues

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

See also

Adjacent entries.