Art Swap Escrow
Allow artists and collectors to trade artworks using blockchain escrow ensuring transaction safety.
Register IP Asset· onchain identity
Section · Onchain
full primer →The primitive.
Every secure art trades artefact is registered as an IP Asset on the DATA Foundation (Aeneid); painters get a canonical ipId, a dispute-provable creator record, and an explorer link that survives platform churn.
Why this primitivethe artefact needs a canonical, dispute-provable identity that survives platform churn
Kernel
a call to @story-protocol/core-sdk `client.ipAsset.registerIpAsset` on DATA Foundation Aeneid (chainId 1315) that mints an SPG NFT and binds it to a new IP Account with full IPA metadata (title, mediaHash, creators, ipType)
Drives the UI as
a 'registered on the DATA Foundation' badge with the ipId, owner address and an aeneid.explorer.datafdn.org link
Required keys.
PRIVY_APP_ID
Publishable — hardcode into src/config.ts. Google sign-in + Aeneid embedded wallet + sponsored tx.
open ↗Hardcode both into src/config.ts (they're publishable, not secrets) before pasting the prompt below.
Appendix · Mega-prompt
The build prompt.
budget · 1 message
Paste into a fresh Lovable project. Make sure src/config.ts holds both values above. read the build strategy →
Build "Art Swap Escrow" in ONE Lovable message. Single-page demo on the DATA Foundation (Aeneid testnet).
CONCEPT
Allow artists and collectors to trade artworks using blockchain escrow ensuring transaction safety.
Discipline: Visual Art (secure art trades).
On-chain primitive: Register IP Asset (onchain identity). Why this primitive: the artefact needs a canonical, dispute-provable identity that survives platform churn
ipType: Artwork
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 (secure art trades artefact -> registered IP Asset on Aeneid):
const ipMetadata = story.ipAsset.generateIpMetadata({
title: "Art Swap Escrow",
description: "Allow artists and collectors to trade artworks using blockchain escrow ensuring transaction safety.",
createdAt: Math.floor(Date.now() / 1000).toString(),
ipType: "Artwork",
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: ["secure art trades", "Artwork"],
});
const { ipId, txHash, tokenId } = await story.ipAsset.registerIpAsset({
nft: { type: 'mint', spgNftContract: '0xc32A8a0FF3beDDDa58393d022aF433e78739FAbc' },
ipMetadata,
});
// Show the IP Asset on the DATA Foundation explorer:
// https://aeneid.explorer.datafdn.org/ipa/${ipId}
USER FLOW
1. Land on page -> 'Sign in with Google' (Privy) -> embedded wallet auto-provisioned on Aeneid.
2. After the user finalises a secure art trades artefact, register it as an IP Asset. Render a 'registered on the DATA Foundation' badge with the ipId, owner and an aeneid.explorer.datafdn.org link. Cache the ipId in local state — downstream steps (license terms, license mint, CDR gating) all reference it.
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
Market sizing.
TAM
$1B
art trading market
SAM
$300M
online art trade platforms
SOM
$30M
blockchain escrow adopters in art
Indicative figures for hackathon pitches — refine with your own research before raising.
Adjacent entries.
art ownership tracking
Provenance Palette
Securely track and prove the ownership lineage of paintings with tamper-proof blockchain records.
generative art mintingGenerative Mint Studio
Artists can mint unique generative art pieces directly onto the DATA Foundation with automated smart contracts.
gallery exhibition trackingExhibit Chain Ledger
Record every artwork's exhibition history securely and transparently onchain for galleries and collectors.
color authenticity validationColorCode Certifier
Certify and verify exact color compositions of artworks using blockchain color code hashes.