🎮 Game Design & Interactive Media · game narrative design · paywall

Provenance Playbooks · MPP

Pay-to-unlock version of Provenance Playbooks: gate the game narrative design action behind a 0.05 AlphaUSD MPP charge on Tempo Moderato, verified on-chain via a TIP-20 Transfer event before the deliverable is returned.

MPP charge · AlphaUSD· pay-to-unlock
Section · Onchain

The primitive.

full primer →

The onchain primitive runs at the right moment in the flow and surfaces a clear, verifiable result that game designers can act on without web3 jargon.

Why this primitiveMonetize game narrative design unlocks per-call — no accounts, no invoices, no Stripe. Agent-payable and human-payable through the same HTTP 402 contract.

Kernel
an HTTP endpoint that returns 402 with an MPP WWW-Authenticate challenge; the client pays 0.05 AlphaUSD via a TIP-20 transfer on Tempo Moderato, the server verifies the on-chain Transfer event, then runs the side effect
Drives the UI as
a 'Pay 0.05 USD · Tempo' button that opens Privy, submits the tx, and reveals the unlocked content with a Tempo Explorer /receipt/… link
Appendix · Secrets

Required keys.

LOVABLE_API_KEY
Auto-provisioned by Lovable. Powers the Lovable AI Gateway (chat, image, embeddings). No action required — keep it server-side.
open ↗
METAMASK_PRIVATE_KEY
Tempo Moderato deployer key exported from MetaMask. Fund via the Tempo testnet faucet.
open ↗
TEMPO_RPC_URL
https://rpc.moderato.tempo.xyz (chainId 42431). Also expose as VITE_TEMPO_RPC_URL for the client.
open ↗
PRIVY_APP_ID
Enables Google sign-in and sponsored transactions on Tempo Moderato.
open ↗
PINATA_JWT
Pins images / JSON / manifests to IPFS.
open ↗

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

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 "Provenance Playbooks · MPP" in ONE Lovable message. Single-page demo.

CONCEPT
Pay-to-unlock version of "Provenance Playbooks": gate the game narrative design action behind a 0.05 AlphaUSD
charge on Tempo Moderato using the MPP (Machine Payments Protocol) 402 flow.
Discipline: Game Design & Interactive Media (game narrative design).
Onchain primitive: MPP charge · AlphaUSD.
Why this primitive: Monetize game narrative design unlocks per-call — no accounts, no invoices, no Stripe. The MPP 402 flow makes the endpoint agent-payable and human-payable through the same HTTP contract.

5-CREDIT BUDGET (HARD LIMIT):
- ONE single-page app + ONE server route. No Lovable Cloud unless the side effect needs a database.
- Privy is the auth + signing + gas-sponsorship layer (Google + email login, embedded EVM
  wallet on Tempo Moderato, native Privy paymaster covers gas via `sponsor: true`).
- viem for chain reads + TIP-20 transfer encoding. No hardhat, no custom contract — you're paying
  a token that already exists (AlphaUSD).
- At most ONE AI call per user action, routed through Lovable AI Gateway using LOVABLE_API_KEY
  from the server handler (never the browser).
- Skip tests, skip CI, skip docs pages. Ship the demo, nothing else.

TEMPO MODERATO — non-obvious rules (bake ALL of these in, they are the difference between
"works first try" and "silently broken"):

1. Network. chainId=42431, RPC=https://rpc.moderato.tempo.xyz,
   explorer=https://explore.testnet.tempo.xyz, faucet=https://faucet.moderato.tempo.xyz.
   Native currency is AlphaUSD (symbol USD, 6 decimals). Mainnet is chainId 4217 — testnet only.

2. Explorer path is /receipt/{hash} — NOT /tx/{hash}. Etherscan-style /tx/... renders
   "Page Not Found" even for confirmed txs. Every transaction link (server response AND UI)
   must use `${TEMPO_EXPLORER}/receipt/${txHash}`.

3. Tempo DISALLOWS native value transfers. `sendTransaction({ to, value })` with any non-zero
   `value` reverts. All payments — including "native" AlphaUSD as it shows in wallets — move as
   TIP-20 `transfer(to, amount)` calls on the AlphaUSD contract:
     ALPHAUSD_ADDRESS = 0x20c0000000000000000000000000000000000001  (6 decimals)
   0.05 USD = 50_000n units. On the client, `encodeFunctionData` the transfer and send with
   `value: 0n` to the TOKEN address, not the recipient. On the server, verify by parsing the
   ERC-20 `Transfer` event from `receipt.logs` — NEVER inspect `tx.value`.

4. MPP 402-challenge shape. Unpaid POSTs must return HTTP 402 with BOTH:
   - Header: `WWW-Authenticate: MPP intent="charge", chainId=42431, recipient="0x…",
     amount="0.05", currency="USD", asset="native"`
   - JSON body echoing the full challenge:
     { scheme:"MPP", intent:"charge", version:"1", mode:"live", network:"tempo-moderato",
        recipient: MPP_RECIPIENT, chainId: 42431, asset:"native", amount:"0.05",
        amountUnits:"50000", currency:"USD", description:"Provenance Playbooks · pay-per-call unlock" }
   The client sends the tx hash back in header `X-Payment-Tx`. `amount` is human-readable,
   `amountUnits` is atomic — ship BOTH and verify against `amountUnits`.

5. Server verification MUST check FIVE things in order (missing any is a real bug, not a nit):
   (a) receipt.status === "success"
   (b) tx.chainId === 42431
   (c) tx.to.toLowerCase() === ALPHAUSD_ADDRESS.toLowerCase()
   (d) tx.from.toLowerCase() === expectedPayer.toLowerCase()   ← binds the tx to the caller;
       without this, anyone can replay someone else's payment tx
   (e) receipt has a Transfer log emitted by AlphaUSD with args.to === MPP_RECIPIENT and
       args.value >= 50_000n
   Use viem:
     import { parseEventLogs, parseAbi } from "viem";
     const abi = parseAbi(["event Transfer(address indexed from, address indexed to, uint256 value)"]);
     const transfers = parseEventLogs({ abi, eventName: "Transfer", logs: receipt.logs })
       .filter(l => l.address.toLowerCase() === ALPHAUSD_ADDRESS.toLowerCase());

6. Privy config. Use `@privy-io/react-auth` with:
     loginMethods: ["google", "email"]
     embeddedWallets: { ethereum: { createOnLogin: "users-without-wallets" } }
   Pass a REAL viem `defineChain` to BOTH defaultChain AND supportedChains — a `{ id, name } as never`
   stub compiles but breaks embedded-wallet transport init:
     import { defineChain } from "viem";
     const tempoModerato = defineChain({
       id: 42431, name: "Tempo Moderato",
       nativeCurrency: { name: "AlphaUSD", symbol: "USD", decimals: 6 },
       rpcUrls: { default: { http: ["https://rpc.moderato.tempo.xyz"] } },
       blockExplorers: { default: { name: "Tempo Explorer", url: "https://explore.testnet.tempo.xyz" } },
     });

7. Privy dashboard toggle + native gas sponsorship. In Privy → Chains, enable "Allow
   transactions from the client" for Tempo Testnet AND turn on native gas sponsorship for
   Tempo Testnet (Privy → Gas Sponsorship → add Tempo Testnet, unlimited or capped policy).
   Without the client-tx toggle, `sendTransaction` hangs forever with no error. Without
   sponsorship, the embedded wallet needs a gas balance and users hit "insufficient funds"
   before the TIP-20 transfer ever lands. Every `sendTransaction` call MUST pass
   `sponsor: true` in the options so Privy's paymaster covers gas — the user only ever
   sees the 0.05 USD price, never a gas prompt. Wrap the Privy send in a 45s
   `Promise.race` timeout whose reject message names the client-tx setting:
     const timeout = new Promise((_, r) => setTimeout(() => r(new Error(
       "Timed out after 45s waiting for Privy. Check that 'Allow transactions from the client' " +
       "is enabled for Tempo Testnet in the Privy dashboard."
     )), 45_000));
     const receipt = await Promise.race([sendTransaction(...), timeout]);

8. Privy mount lazy + client-only. `@privy-io/react-auth` explodes under SSR. Wrap
   `PrivyProvider` in `lazy(() => import("./privy-client-entry"))` inside `<ClientOnly>` +
   `<Suspense>`. Never import the Privy package at the module scope of a route file.

9. Signing pattern. Read the embedded wallet from `useWallets()` (filter
   `walletClientType === "privy"`), then call `useSendTransaction()` with sponsorship:
     const data = encodeFunctionData({ abi: TIP20, functionName: "transfer",
                                       args: [MPP_RECIPIENT, 50_000n] });
     const receipt = await sendTransaction(
       { to: ALPHAUSD_ADDRESS, data, value: 0n, chainId: 42431 },
       { address: embedded.address, sponsor: true },   // ← native Privy gas sponsorship
     );
     const txHash = receipt.hash;
   The `sponsor: true` flag is non-negotiable — Tempo verification still passes because
   the tx is submitted FROM the embedded wallet (tx.from === payer); Privy's paymaster
   only pays the gas, it does not change the sender.

10. User-facing copy is walletless. "Sign In" not "Connect Wallet"; "Account" not "Wallet";
    show the AlphaUSD balance as "0.0501 USD" (formatUnits with 6 decimals, .toFixed(4)) not
    the raw uint. Hide "gas" language entirely — the user only sees the 0.05 USD price.

REFERENCE CONFIG (src/lib/mpp/config.ts) — copy verbatim:
```ts
export const MPP_RECIPIENT = "0x7f282a0001c8D189457E187a860ae6E490f6508E" as const; // your payout wallet
export const TEMPO_CHAIN_ID = 42431;
export const TEMPO_RPC_URL = "https://rpc.moderato.tempo.xyz";
export const TEMPO_EXPLORER = "https://explore.testnet.tempo.xyz";
export const TEMPO_FAUCET_URL = "https://faucet.moderato.tempo.xyz";
export const ALPHAUSD_ADDRESS = "0x20c0000000000000000000000000000000000001" as const;
export const CHARGE_AMOUNT_USD = "0.05";
export const CHARGE_AMOUNT_UNITS = 50_000n; // 0.05 * 10^6
export const CHARGE_DESCRIPTION = "Provenance Playbooks · pay-per-call unlock";
```

SERVER ROUTE (src/routes/api/public/provenance-playbooks.ts)
- GET  → returns the JSON challenge (schema in rule 4).
- POST → reads header `X-Payment-Tx`, validates the body with zod ({ payer: 0x-address,
  ...idea-specific input }), and if the tx header is missing returns 402 + WWW-Authenticate
  header + challenge JSON body. Otherwise imports the verifier from a `.server.ts` file
  (dynamic import inside the handler to keep viem out of the client bundle), runs the five
  checks from rule 5, and on success executes the SIDE EFFECT below.

SIDE EFFECT (the paid action for this specific idea)
After payment clears, pin the user's game narrative design artefact to IPFS via Pinata (PINATA_JWT server-side) and mint an ERC-721 receipt on Tempo Moderato pointing at the CID — the tokenId + Tempo Explorer link are the unlocked deliverable.
Only run this after check 5(e) passes. AI calls (if any) use `process.env.LOVABLE_API_KEY`
inside the server handler with `@ai-sdk/openai-compatible` pointed at
`https://ai.gateway.lovable.dev/v1` (header `Lovable-API-Key`) — never send the key to the
browser. Default model: google/gemini-3-flash-preview.

USER FLOW
1. Land on page → "Sign in with Google" (Privy) → embedded wallet auto-provisioned on
   Tempo Moderato. Show the address and current AlphaUSD balance (viem `balanceOf` on
   ALPHAUSD_ADDRESS, poll every 15s, formatUnits(balance, 6).toFixed(4) + " USD").
2. Show a link to https://faucet.moderato.tempo.xyz with a "Refresh balance" button — never
   assume the user has been funded.
3. User enters the game narrative design input and clicks "Pay 0.05 USD · Tempo".
4. Client GET /api/public/provenance-playbooks → parses 402 challenge → encodes TIP-20 transfer →
   Privy signs + sends (wrapped in the 45s timeout) → POSTs { payer, ...input } with
   `X-Payment-Tx: <txHash>`.
5. Server verifies (five checks) → runs the SIDE EFFECT → returns
   { result, payer, txHash, chainId: 42431, recipient: MPP_RECIPIENT,
      explorerUrl: `${TEMPO_EXPLORER}/receipt/${txHash}` }.
6. UI reveals the result and links "View receipt on Tempo Explorer" to `explorerUrl`.
7. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14"

REQUIRED SECRETS (Lovable → Project Settings → Secrets):
- LOVABLE_API_KEY   Auto-provisioned by Lovable. Powers the Lovable AI Gateway. Server-only.
- PRIVY_APP_ID      Google + email sign-in + embedded EVM wallet on Tempo Moderato.
- PINATA_JWT        Only if the side effect pins media to IPFS (rule-dependent).
- MPP_RECIPIENT is a PUBLIC payout address baked into src/lib/mpp/config.ts — no secret needed.

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

Market sizing.

TAM
$3B
narrative-driven game market · pay-per-call
SAM
$500M
indie narrative game developers · pay-per-call
SOM
$50M
studios adopting blockchain for storytelling · pay-per-call

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

See also

Adjacent entries.