🎞️ Film & Animation · festival submissions

Festival Submissions Split v3

A film & animation tool that turns festival submissions into a one-click, gas-sponsored EVVM payment.

Split payment· evvm
Section · Onchain

The primitive.

full primer →

One signed message from indie filmmakers pays every festival submissions recipient atomically via EVVM's split primitive; the fisher settles the whole payout for zero user gas.

Why this primitiveOne signed message pays many recipients atomically via Core.split.

Kernel
One signed message pays many recipients atomically via Core.split.
Drives the UI as
Enter a payee list with shares; one sponsored click settles everyone.
Appendix · Secrets

Required keys.

METAMASK_PRIVATE_KEY
Sepolia deployer + fisher wallet (single-EOA model). Fund via the Google Cloud faucet.
open ↗
SEPOLIA_RPC_URL
Alchemy Sepolia HTTPS endpoint. Public RPCs throttle under hackathon load.
open ↗
ETHERSCAN_API_KEY
Required for forge verify-contract after deploy.
open ↗
PRIVY_APP_ID
Enables Google sign-in and sponsored transactions. Toggle Gas sponsorship → Sepolia in the dashboard.
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 "Festival Submissions Split v3" in ONE Lovable message. Single-page demo on Ethereum Sepolia.

CONCEPT
A film & animation tool that turns festival submissions into a one-click, gas-sponsored EVVM payment.
Discipline: Film & Animation (festival submissions).
EVVM primitive: Split payment. Why: One signed message pays many recipients atomically via Core.split.

EVVM STANDING ORDERS (self-contained — do NOT rely on any external skill)

Chain: Ethereum Sepolia (chainId 11155111). Auth: Privy (Google/email/wallet), embedded wallet.
Gas: Native Privy sponsorship — enable in Privy dashboard → Gas sponsorship → App pays → Sepolia →
      "Allow transactions from the client".

Signing rule: EVVM messages are EIP-191 personal_sign of a CSV STRING (not abi-encoded):
    "{evvmId},{senderExecutor},{hashPayload},{originExecutor},{nonce},{isAsyncExec}"
  Addresses lowercase, booleans literal "true"/"false".

hashPayload rule: hashPayload = keccak256(abi.encode("<functionName>", ...on-chain hash args ONLY))
  Include ONLY the fields the on-chain hashDataForX view hashes — nothing else. Do NOT include
  evvmId, nonce, or isAsyncExec in the hash; they live in the outer CSV. Example for Core.pay
  it is 5 args exactly:
    keccak256(abi.encode("pay", to, identity, token, amount, priorityFee))
  A wrong shape reverts InvalidSignature (selector 0x8baa579f) with no useful message. Always
  cross-check locally against the hashDataForX view before shipping.

Sender rules (multi-leg flows):
- Single-leg op:   senderExecutor = 0x0
- Two-leg (MNS reveal, staking, swap, split):
    op leg  → senderExecutor = 0x0
    pay leg → senderExecutor = address of the CALLING contract (NameService / Staking / Swap / Core)

Nonces: random uint256 from 32 bytes of crypto.getRandomValues; isAsyncExec = true.

Browser-signing rule: any op that binds a user 'from' address (deposit, withdraw, split, MNS, staking,
swap) MUST be signed by the connected Privy wallet. The fisher route re-derives the expected message,
runs verifyMessage against the user's address, then broadcasts with its own EOA paying gas.

Privy mount rule: NEVER import @privy-io/react-auth at module scope of a route file.
  Wrap in <ClientOnly><Suspense><LazyPrivyEntry/></Suspense></ClientOnly>. PrivyProvider config:
    { loginMethods:["google","email","wallet"],
      embeddedWallets:{ ethereum:{ createOnLogin:"users-without-wallets" } },
      defaultChain: sepolia, supportedChains:[sepolia] }
  DO NOT pass uiOptions:{ showWalletUIs:false } — aborts with "signal is aborted without reason".
  DO NOT wire ZeroDev/paymaster URLs — native Privy sponsorship handles it.

Sponsored host-chain sends (Fund-EVVM, any host-chain user tx):
  await sendTransaction(
    { to, data, chainId: 11155111 },
    { sponsor: true, address: embedded.address }
  );
  Both fields (sponsor + address) are required or Privy silently falls back to a user-paid path.

Fund-EVVM is a TWO-STEP host-chain flow (both via the sponsored send above):
  1. USDC.approve(Treasury, amount)      // atomic USDC units, uint256
  2. Treasury.deposit(USDC, amount)      // pulls the approved amount into EVVM
  Refetch Core.getBalance(user, USDC) after settle. Show approve+deposit as two buttons.

Decimal USDC UX (non-negotiable):
- Inputs: <input inputMode="decimal" defaultValue="0.01" /> — never "USDC (micro)" labels.
- Parse:  parseUnits(str.trim(), 6)   // → bigint atomic
- Format: `${formatUnits(wei, 6)} USDC`
- Never render raw atomic values like "10000" in the UI. Contracts still receive the atomic bigint.

Balance preflight (mandatory before ANY user-signed op that spends inside EVVM — pay, split, MNS
pay leg, staking, swap, x402 settle, shielded deposit): read Core.getBalance(user, token) on mount
and after every settle. Gate the submit button when balance < required and render an amber
"Insufficient EVVM USDC — top up first" notice pointing at the Fund-EVVM card. Skipping this
produces InsufficientBalance (selector 0xf4d678b8) reverts the user can't self-diagnose.

Deployment (in a Lovable Linux sandbox — participants deploy from THEIR project, not locally):
1. bash scripts/install-foundry.sh    # installs foundry via Nix into the sandbox
2. node scripts/deploy-evvm.mjs       # deploys the 6-contract EVVM stack + registers evvmId
   Uses METAMASK_PRIVATE_KEY as deployer AND admin AND goldenFisher AND activator (single-EOA model).
   Writes src/evvm/deployment.json { evvmId, core, staking, nameService, swap, treasury, usdc, chainId }.
3. node scripts/set-evvm-id.mjs       # CRITICAL — Registry.register assigns the id but Core still
   reports evvmId=0 until an admin calls Core.setEvvmID(assignedId). Every user signature (built
   with the Registry id) reverts InvalidSignature (0x8baa579f) until this runs. Re-run whenever
   Core is redeployed. The script reads evvmId from src/evvm/deployment.json and signs with
   METAMASK_PRIVATE_KEY (the single-EOA admin).

Fisher: server-side viem wallet at src/lib/fisher.server.ts, uses METAMASK_PRIVATE_KEY.
Fisher routes: src/routes/api/public/fisher/*.ts (the /api/public/* prefix bypasses site auth on
published builds — verify signatures inside every handler).

Fisher response contract (every fisher route returns this exact shape):
  { ok: boolean, txHash?: `0x${string}`, explorer?: string, revert?: string, error?: string }
  Build `explorer` server-side: `${evvmCfg.explorer}/tx/${txHash}`. On success the UI renders
  a clickable "↗ View on Etherscan · <txHash slice>". On failure surface `revert` (decoded
  selector) or `error` verbatim in the step log — do NOT swallow it.

Common revert selectors — decode and show human strings, do NOT show raw 0x-selectors:
  InvalidSignature         0x8baa579f  → wrong hashPayload shape, or Core.setEvvmID never ran
  InsufficientBalance      0xf4d678b8  → payer EVVM balance too low; preflight must have caught this
  ProofLengthWrongWithLogN 0x59895a53  → zkVault verifier ↔ bb.js version mismatch

REQUIRED SECRETS:
- PRIVY_APP_ID              — Privy dashboard → App settings
- METAMASK_PRIVATE_KEY      — Sepolia EOA that deploys + fishes; fund via https://cloud.google.com/application/web3/faucet/ethereum/sepolia
- SEPOLIA_RPC_URL           — Alchemy Sepolia HTTPS URL (public RPCs throttle under load)
- ETHERSCAN_API_KEY         — for verification via forge verify-contract

CREDIT LINE (must appear in UI footer AND as a NatSpec @notice on any deployed contract):
  Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14.


MINIMUM VIABLE FLOW
1. Landing page. "Sign in with Google" via Privy — embedded wallet is auto-provisioned.
2. Show the user their EVVM USDC balance. If zero, render the fund-EVVM empty-state (address + copy
   button + Circle faucet link + refresh).
3. Primary action: "Festival Submissions Split v3" — collect the inputs relevant to festival submissions.
4. On submit:
   a. Client builds hashPayload = keccak256(abi.encode("<functionName>", ...typedArgs)) exactly per
      the on-chain hashDataForX view for this flow.
   b. Client builds the CSV message with the sender rules above (single-leg or two-leg as required
      by "Split payment") and calls personal_sign via Privy.
   c. Client POSTs { user, signature, args } to /api/public/fisher/<flow>.
   d. Fisher route re-derives the expected message, verifyMessage's the user signature, then submits
      the tx with METAMASK_PRIVATE_KEY (see src/lib/fisher.server.ts). Return the tx hash.
5. UI shows a Sepolia Etherscan link for the tx and a receipt tuned to "festival submissions".

CONTRACTS: reuse the deployed EVVM stack — see src/evvm/deployment.json. Do NOT redeploy per-idea.
If deployment.json.deployed is false, run: node scripts/deploy-evvm.mjs (from a Lovable sandbox
terminal, NOT from a user's laptop).

TAILORING NOTES for "festival submissions":
- Copy, iconography, and success states should read as Film & Animation content, not generic web3.
- Use the discipline vocabulary the audience already knows (indie filmmakers, animators, studios, VFX houses).
- Keep the whole build to ONE index route + ONE fisher route.

CREDIT (footer + on-chain @notice):
  Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14.
Appendix · Market

Market sizing.

TAM
the film & animation market (~$95B, 50k studios worldwide)
SAM
A gas-sponsored payments slice of Film & Animation
estimate 10% of TAM.
SOM
The indie filmmakers early-adopter segment
estimate 1% of TAM in year one.

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

See also

Adjacent entries.