🎨 Visual Art · digital art rendering

Broker Challenge

Metered digital art rendering unlock on Tempo Moderato. Native value transfers revert on Tempo, so the client encodes a TIP-20 transfer to the AlphaUSD contract and the server verifies the Transfer event.

Tempo MPP· verifiable AlphaUSD receipt
Section · Agentic

The kernel.

full primer →

An agent runs the protocol server-side and painters get a signed, auditable result they can act on without leaving the app.

Why this primitiveTempo MPP on Tempo Moderato fits digital art rendering in Visual Art because painters can settle 0.05 AlphaUSD via a TIP-20 transfer with an on-chain Transfer receipt before the broker endpoint returns anything.

Kernel
pay → receipt → side effect on Tempo Moderato (chainId 42431) — the server 402s with a `WWW-Authenticate: MPP` challenge, the client pays AlphaUSD via TIP-20 `transfer` on `0x20c0…0001`, the server verifies the `Transfer` log against the payer and amount, and only then runs the unlock
Drives the UI as
a Pay & Unlock button that streams Challenge · Sign Transfer · Verify · Unlock into the flow log, with a `/receipt/{hash}` link on the Tempo explorer
Appendix · Secrets

Required key.

LOVABLE_API_KEY
Auto-provisioned by Lovable when you enable the Lovable AI add-on. Powers the buyer, seller and merchant agents through an OpenAI-compatible gateway in front of every frontier LLM. Zero copy-paste.
open ↗

Add this 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 the key above is set first. read the build strategy →

Build "Broker Challenge · mpp" as a ONE-SHOT Lovable build. The participant has only
5 credits — this single message must produce a working demo with no follow-ups.
Single-page TanStack Start app. Cut scope ruthlessly.

CONCEPT
Lovable AI runs the agent that buys spot GPU rendering time via AP2 so generative artists process massive datasets cheaply.
Reframed as a paid, agent-callable HTTP endpoint on Tempo Moderato MPP (0.05 AlphaUSD via TIP-20 transfer + pay-receipt-effect verification).
Discipline: Visual Art (digital art rendering).

LOVABLE BUDGET (HARD CAP: ONE-SHOT, ~5 CREDITS TOTAL):
The participant has FIVE Lovable credits for the whole build. This prompt MUST
ship a working demo on the FIRST message with zero follow-ups. Engineer for that.
- ONE TanStack Start app, ONE route (`src/routes/index.tsx`). No extra pages, no auth, no nav.
- ONE TanStack server route under `src/routes/api/public/` that runs the protocol.
- ONE client surface (a button + flow log) that triggers the payment and renders the result.
- NO database, NO Lovable Cloud, NO extra integrations beyond what's listed below.
- NO tests, NO docs pages, NO settings screens.
- Demo-fallback contract: every code path must boot with NO secrets and return
  realistic simulated envelopes flagged `simulated: true`. Real signing activates
  only when the user signs in and the wallet is funded.

PROTOCOL SURFACE — MPP on Tempo Moderato (self-contained; participant has no skills)

CONFIG (src/lib/mpp/config.ts) — copy exactly:
```ts
export const MPP_RECIPIENT      = process.env.MPP_RECIPIENT ?? "0x0000000000000000000000000000000000000000";
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 (AlphaUSD is 6 decimals)
```

TEMPO MODERATO GOTCHAS — get these wrong and it silently fails:

1. Explorer path is `/receipt/{hash}`, NOT `/tx/{hash}`. Every UI link and every
   server response uses `${TEMPO_EXPLORER}/receipt/${txHash}`. `/tx/…` renders
   "Page Not Found" with no redirect on Tempo's explorer.

2. Tempo DISALLOWS native value transfers. `sendTransaction({ to, value })` with
   any non-zero `value` reverts. All AlphaUSD payments move as TIP-20
   `transfer(to, amount)` calls on the AlphaUSD contract:
   ```ts
   import { encodeFunctionData, parseAbi } from "viem";
   const data = encodeFunctionData({
     abi: parseAbi(["function transfer(address to, uint256 amount)"]),
     functionName: "transfer",
     args: [MPP_RECIPIENT, CHARGE_AMOUNT_UNITS],
   });
   const txHash = await provider.sendTransaction({
     to: ALPHAUSD_ADDRESS,   // TOKEN address, not recipient
     data,
     value: 0n,              // MUST be zero
   });
   ```

3. Server verification MUST check ALL FIVE in this order — missing any is a
   silent-accept bug:
   (a) `receipt.status === "success"`
   (b) `tx.chainId === 42431`
   (c) `tx.to.toLowerCase() === ALPHAUSD_ADDRESS`
   (d) `tx.from.toLowerCase() === expectedPayer.toLowerCase()`  ← replay guard
   (e) Receipt has a `Transfer(from, to, value)` log emitted by `ALPHAUSD_ADDRESS`
       with `args.to === MPP_RECIPIENT` and `args.value >= CHARGE_AMOUNT_UNITS`.
   Use viem's `parseEventLogs` + `parseAbi(["event Transfer(address indexed from, address indexed to, uint256 value)"])`
   and filter by `log.address === ALPHAUSD_ADDRESS`. Missing (d) lets anyone
   replay someone else's payment tx.

4. Privy dashboard: enable "Allow transactions from the client" for Tempo Testnet
   under Chains. Without it, `sendTransaction` HANGS FOREVER with no error.
   Wrap the call in a 45s `Promise.race` timeout whose rejection message names
   this exact dashboard setting.

5. Privy chain config MUST be a real viem `defineChain`, NOT a `{id, name} as never`
   stub — the stub compiles but breaks embedded-wallet transport init. Define
   once and pass to BOTH `defaultChain` and `supportedChains`:
   ```ts
   import { defineChain } from "viem";
   export const tempoModerato = defineChain({
     id: 42431,
     name: "Tempo Moderato",
     nativeCurrency: { name: "AlphaUSD", symbol: "USD", decimals: 6 },
     rpcUrls: { default: { http: [TEMPO_RPC_URL] } },
     blockExplorers: { default: { name: "Tempo Explorer", url: TEMPO_EXPLORER } },
   });
   ```

6. Privy mount is SSR-forbidden. `@privy-io/react-auth` explodes under SSR —
   load it via `lazy(() => import("./privy-client-entry"))` inside
   `<ClientOnly>` + `<Suspense>` in `src/components/privy-root.tsx`. NEVER
   import the Privy package at the module scope of a route file.

PAY → RECEIPT → EFFECT (the entire trust story):

SERVER ROUTE (src/routes/api/public/mpp-charge.ts) — the side effect ONLY runs
after receipt verification. Preserve HTTP 402 + `WWW-Authenticate` end-to-end.

Unpaid POST (no `X-Payment-Tx` header) — return the 402 challenge:
```
HTTP/1.1 402 Payment Required
WWW-Authenticate: MPP intent="charge", chainId=42431, recipient="0x…", amount="0.05", currency="USD", asset="native"
Content-Type: application/json

{ "scheme": "MPP", "intent": "charge", "network": "tempo-moderato",
  "recipient": MPP_RECIPIENT, "chainId": 42431, "asset": "native",
  "amount": "0.05", "amountUnits": "50000", "currency": "USD",
  "description": "Unlock the digital art rendering pack" }
```
Ship BOTH the `WWW-Authenticate` header AND the JSON body — mppx clients need
the header to retry-with-payment; skipping it makes the endpoint useless.
NEVER map 402 → 500. `amount` is human-readable ("0.05"); `amountUnits` is the
atomic string ("50000"). Servers verify against `amountUnits`; UIs display `amount`.

Paid POST (`X-Payment-Tx: 0x…` + `X-Payer: 0x…`) — verify then execute:
```ts
import { createPublicClient, http, parseAbi, parseEventLogs } from "viem";
import { tempoModerato, ALPHAUSD_ADDRESS, MPP_RECIPIENT, CHARGE_AMOUNT_UNITS, TEMPO_CHAIN_ID } from "@/lib/mpp/config";

const client = createPublicClient({ chain: tempoModerato, transport: http(TEMPO_RPC_URL) });
const [tx, receipt] = await Promise.all([
  client.getTransaction({ hash: txHash }),
  client.getTransactionReceipt({ hash: txHash }),
]);

if (receipt.status !== "success") return json402("tx_not_success");
if (tx.chainId !== TEMPO_CHAIN_ID) return json402("wrong_chain");
if (tx.to?.toLowerCase() !== ALPHAUSD_ADDRESS.toLowerCase()) return json402("wrong_token");
if (tx.from.toLowerCase() !== expectedPayer.toLowerCase()) return json402("payer_mismatch");

const transfers = parseEventLogs({
  abi: parseAbi(["event Transfer(address indexed from, address indexed to, uint256 value)"]),
  logs: receipt.logs,
}).filter((l) => l.address.toLowerCase() === ALPHAUSD_ADDRESS.toLowerCase());

const paid = transfers.some((l) =>
  l.args.to.toLowerCase() === MPP_RECIPIENT.toLowerCase() &&
  l.args.value >= CHARGE_AMOUNT_UNITS,
);
if (!paid) return json402("insufficient_payment");

// side effect happens ONLY past this point
return Response.json({
  ok: true,
  txHash,
  receipt: `${TEMPO_EXPLORER}/receipt/${txHash}`,
  unlocked: { /* the deliverable for digital art rendering */ },
});
```

USER FLOW (the entire app):
1. Sign In (email/google) — Privy creates an embedded EVM wallet on Tempo Moderato.
2. Show the wallet address + `TEMPO_FAUCET_URL` link.
3. "Pay & unlock" button — POSTs to `/api/public/mpp-charge`, catches the 402,
   parses the challenge, calls the TIP-20 transfer, then re-POSTs with
   `X-Payment-Tx` + `X-Payer` headers. Show a live log: Challenge · Sign
   Transfer · Verify · Unlock.
4. On success, render the unlocked deliverable + a link to
   `${TEMPO_EXPLORER}/receipt/${txHash}`.

SIMULATE MODE (default when signed out): the flow walks the four steps with a
fake `0x…` tx hash flagged `simulated: true`, no real chain call, no signing
prompt. The button label switches from "Pay & unlock (simulate)" to
"Pay & unlock (live · Tempo Moderato)" once the wallet is ready and funded.

STACK
- TanStack Start app, the index route only.
- Tailwind + shadcn. Editorial look: gold accent on a dark or warm-cream background,
  generous type, one strong headline, one primary action.
- Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14".
- Libraries: template defaults + `@privy-io/react-auth` + `viem` + `zod`. Nothing else.

CREDIT (UI footer + JSDoc on the server handler): Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
Appendix · Market

Market sizing.

TAM
$3B
cloud rendering and compute services
SAM
$600M
generative art and VFX rendering
SOM
$30M
automated spot compute buying

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

See also

Adjacent entries.