🎥 Videography & Film · subtitling and captions

CaptionCast IPFS

Store and distribute subtitle files on IPFS for reliable, versioned caption accessibility worldwide.

IPFS via Pinata· decentralized storage
Section · Onchain

The primitive.

full primer →

Every subtitling and captions artefact is pinned to IPFS through Pinata; videographers get a permanent CID and a public gateway preview instead of a fragile cloud URL.

Why this primitivePinata’s IPFS pinning ensures subtitles are immutable, accessible, and easily shared across platforms.

Kernel
a Pinata JWT upload that pins images / JSON / manifests to IPFS and returns a permanent CID
Drives the UI as
a 'pinned to IPFS' chip with the CID and an ipfs.io gateway preview
Appendix · Secrets

Required keys.

METAMASK_PRIVATE_KEY
Exported from MetaMask. Fund on Arbitrum Sepolia via the Coinbase faucet.
open ↗
ARBITRUM_SEPOLIA_RPC_URL
Alchemy Arbitrum Sepolia HTTPS endpoint (or https://sepolia-rollup.arbitrum.io/rpc).
open ↗
ETHERSCAN_API_KEY
Single Etherscan v2 key — covers Arbiscan (chainId 421614) with no extra key.
open ↗
PRIVY_APP_ID
Enables Google sign-in and sponsored Arbitrum Sepolia transactions.
open ↗
PINATA_JWT
Pins images / JSON / manifests to IPFS.
open ↗

Add these in your Lovable project under Settings → Secrets before pasting the prompt below.

Appendix · Mega-prompt

The build prompt.

Paste into a fresh Lovable project. Make sure all five secrets above are set first. read the build strategy →

Build "CaptionCast IPFS" in ONE Lovable message. Single-page demo.

CONCEPT
Store and distribute subtitle files on IPFS for reliable, versioned caption accessibility worldwide.
Discipline: Videography & Film (subtitling and captions).
Onchain primitive: IPFS via Pinata. Why this primitive: Pinata’s IPFS pinning ensures subtitles are immutable, accessible, and easily shared across platforms.

5-CREDIT BUDGET (HARD LIMIT):
- ONE single-page app. No router, no Lovable Cloud, no database, no auth flows beyond Privy drop-in.
- ONE Solidity contract, <=80 lines, deployed to Arbitrum Sepolia (chainId 421614), verified on Arbiscan.
- Privy is always the auth + sponsored-tx layer (Google login, embedded wallet).
- Pinata/IPFS only if the idea genuinely needs to store a file or metadata.
- 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).
- Privy embedded wallet wraps `<App />` in src/main.tsx. `defaultChain` needs
  `as never` because Privy's type is narrow:
    <PrivyProvider appId={import.meta.env.VITE_PRIVY_APP_ID}
      config={{ loginMethods:['google'],
                embeddedWallets:{ethereum:{createOnLogin:'users-without-wallets'}},
                defaultChain: { id: 421614, name:'Arbitrum Sepolia' } as never }}>
- All txs go through Privy sponsorship. BOTH flags are required:
    const { sendTransaction } = useSendTransaction();
    const { wallets } = useWallets();
    const embedded = wallets.find(w => w.walletClientType === 'privy');
    await sendTransaction(
      { to, data, chainId: 421614 },
      { sponsor: true, address: embedded.address },   // BOTH required
    );
  Without `address`, Privy can route through a non-embedded wallet and the
  user sees "Add funds on Arbitrum Sepolia" even on testnet.
- Privy dashboard (one-time): enable Arbitrum Sepolia under Chains AND add a
  Gas Sponsorship (Paymasters) policy for chainId 421614 — "Sponsor all
  transactions" is fine for a hackathon demo. Code flags alone are not enough.
- src/lib/pinata.ts uploads via `fetch('https://api.pinata.cloud/pinning/pinFileToIPFS', { method:'POST', headers:{ Authorization: `Bearer ${import.meta.env.VITE_PINATA_JWT}` }, body: fd })`.
- Hardhat in /contracts (kept outside the Vite bundle). Install
  `@nomicfoundation/hardhat-ethers` AND `@nomicfoundation/hardhat-verify` (>=2.x).
  DO NOT install `@nomicfoundation/hardhat-toolbox` — it drags Hardhat 3 peers.
- hardhat.config.cjs — Arbitrum Sepolia + Etherscan v2 single-key:
    require("@nomicfoundation/hardhat-ethers");
    require("@nomicfoundation/hardhat-verify");
    const pk = process.env.DEPLOYER_PRIVATE_KEY || process.env.METAMASK_PRIVATE_KEY;
    module.exports = {
      solidity: { version: "0.8.24", settings: { optimizer: { enabled: true, runs: 200 } } },
      networks: { arbitrumSepolia: {
        url: process.env.ARBITRUM_SEPOLIA_RPC_URL || "https://sepolia-rollup.arbitrum.io/rpc",
        accounts: pk ? [pk.startsWith("0x") ? pk : "0x" + pk] : [],
        chainId: 421614,
      } },
      etherscan: {
        apiKey: process.env.ETHERSCAN_API_KEY,          // single Etherscan v2 key covers Arbiscan
                                                        // MUST be a plain string, not { arbitrumSepolia: ... }
        customChains: [{
          network: "arbitrumSepolia", chainId: 421614,
          urls: {
            apiURL: "https://api.etherscan.io/v2/api?chainid=421614",
            browserURL: "https://sepolia.arbiscan.io",
          },
        }],
      },
      sourcify: { enabled: false },
    };
- scripts/deploy.cjs — deploy AND write src/data/contract.json so the UI wires
  up with no manual paste:
    const hre = require("hardhat");
    const fs = require("fs"); const path = require("path");
    async function main() {
      const c = await hre.ethers.deployContract("CaptionCastIPFS");
      await c.waitForDeployment();
      const address = await c.getAddress();
      const deployTx = c.deploymentTransaction().hash;
      fs.mkdirSync(path.join(__dirname, "..", "src", "data"), { recursive: true });
      fs.writeFileSync(
        path.join(__dirname, "..", "src", "data", "contract.json"),
        JSON.stringify({
          address, deployTx, chainId: 421614, network: "arbitrumSepolia",
          deployedAt: new Date().toISOString(),
          explorer: `https://sepolia.arbiscan.io/address/${address}`,
        }, null, 2) + "\n",
      );
      console.log("deployed", address);
    }
    main().catch(e => { console.error(e); process.exit(1); });
- Run order (single burst):
    bunx hardhat compile
    bunx hardhat run scripts/deploy.cjs --network arbitrumSepolia
    bunx hardhat verify --network arbitrumSepolia <address>
  On verify success it prints "Successfully verified contract … on the block
  explorer" and the source is readable at
  `https://sepolia.arbiscan.io/address/<address>#code`.
- Frontend reads: create a viem public client with the Arbitrum Sepolia RPC too —
  `createPublicClient({ chain: arbitrumSepolia, transport: http(import.meta.env.VITE_ARBITRUM_SEPOLIA_RPC_URL) })`
  (import `arbitrumSepolia` from `viem/chains`).
  Expose the RPC to the client by also setting VITE_ARBITRUM_SEPOLIA_RPC_URL to the same value.
- The UI imports `src/data/contract.json` directly — no env var, no manual paste —
  and links to `https://sepolia.arbiscan.io/address/<address>`.


CONTRACT (contracts/CaptionCastIPFS.sol):
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @title CIDLogCaptionCastIPFS
/// @notice Store and distribute subtitle files on IPFS for reliable, versioned caption accessibility worldwide.
/// @notice Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
contract CIDLogCaptionCastIPFS {
    event Logged(address indexed author, string cid, uint256 at);
    /// @notice Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
    function log(string calldata cid) external {
        emit Logged(msg.sender, cid, block.timestamp);
    }
}
```

USER FLOW
1. Land on page -> 'Sign in with Google' (Privy) -> embedded wallet auto-provisioned on Arbitrum Sepolia.
2. On submit, pin the subtitling and captions artefact to Pinata, then call `log(cid)` on the contract via Privy sponsored tx. Render the CID, IPFS gateway preview, and Arbiscan tx link.
3. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14"

REQUIRED SECRETS (Lovable -> Project Settings -> Secrets):
- DEPLOYER_PRIVATE_KEY  Arbitrum Sepolia deployer key (alias accepted: METAMASK_PRIVATE_KEY).
                        Fund it: https://faucet.quicknode.com/arbitrum/sepolia
                        (alt: https://www.alchemy.com/faucets/arbitrum-sepolia)
- ARBITRUM_SEPOLIA_RPC_URL  Alchemy Arbitrum Sepolia HTTPS endpoint (https://arb-sepolia.g.alchemy.com/v2/<key>)
                        OR the public default https://sepolia-rollup.arbitrum.io/rpc. Create a free app: https://dashboard.alchemy.com/
- ETHERSCAN_API_KEY     Single Etherscan v2 key — verifies on Arbiscan (chainId 421614) with no extra key.
                        Get: https://etherscan.io/myapikey
- PRIVY_APP_ID          Google sign-in + sponsored tx. In the Privy dashboard you MUST:
                          (1) enable Arbitrum Sepolia (421614) under Chains, AND
                          (2) add a Gas Sponsorship (Paymasters) policy for 421614
                              ("Sponsor all transactions" is fine for a hackathon demo).
                        Code flags alone are NOT enough — both dashboard toggles are required.
                        Docs: https://docs.privy.io/llms-full.txt
- PINATA_JWT            IPFS uploads (only if app pins media). Docs: https://docs.pinata.cloud/llms-full.txt

CREDIT (must appear in UI footer AND as NatSpec on every deployed contract):
Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
Appendix · Market

Market sizing.

TAM
$1.1B
video editing and streaming software
SAM
$90M
captioning and subtitling tools
SOM
$12M
content creators needing accessible captions

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

See also

Adjacent entries.