Chromasettlev · Jetton
Bid on generative color palettes and hex-coded digital assets where every bid is a 0.01 USDT-on-TON the Jetton paywall micropayment. Instead of high-friction gas wars, users 'ink' their interest through streaming micropayments that fund the artist in real-time. Each increment settles on Base, providing proof-of-bid without the overhead of traditional auction houses.
The primitive.
The color-themed art auctions payload sits behind an HTTP 402 wall priced in Jettons; painters pay with one Tonkeeper confirmation carrying a one-time memo, and the server unlocks it only after the transfer settles on TON.
Why this primitiveBy turning the 'bid' into a recurring micropayment, we solve the friction of traditional auctions. The payment becomes the interaction primitive—proving stake and interest without committing to thousands of dollars upfront.
Required keys.
Add these in your Lovable project under Settings → Secrets before pasting the prompt below.
The build prompt.
Paste into a fresh Lovable project. Make sure all five secrets above are set first. read the build strategy →
Build "Chromasettlev · Jetton" in ONE Lovable message. Single-page Jetton-metered paid app on the TON testnet.
CONCEPT
Bid on generative color palettes and hex-coded digital assets where every bid is a 0.01 USDT-on-TON the Jetton paywall micropayment. Instead of high-friction gas wars, users 'ink' their interest through streaming micropayments that fund the artist in real-time. Each increment settles on Base, providing proof-of-bid without the overhead of traditional auction houses.
Discipline: Visual Art (color-themed art auctions).
Onchain primitive: Jetton micropayments on TON (USDT-on-TON shape, TEP-74 transfers). Why: By turning the 'bid' into a recurring micropayment, we solve the friction of traditional auctions. The payment becomes the interaction primitive—proving stake and interest without committing to thousands of dollars upfront.
5-CREDIT BUDGET (HARD LIMIT)
- ONE single-page app. No Lovable Cloud, no database, no auth beyond TON Connect.
- NO custom smart contract. Payments settle as ordinary TEP-74 Jetton transfers to your receiver wallet.
- Self-host the verifier as ONE TanStack server route inside the same app (shape below).
- At most ONE AI call per user action (Lovable AI Gateway with LOVABLE_API_KEY, only if AI is part of the idea).
- Skip tests, skip CI, skip docs. Ship the demo.
STACK
- React + Vite + TanStack Start, single page (the index route).
- SSR-safe wallet mount is MANDATORY. Never import @tonconnect/ui-react at module
scope of a route file — it touches `window` and crashes SSR. Use
lazy(() => import('./ton-client-entry')) inside <ClientOnly> + <Suspense>, and
keep <TonConnectUIProvider> inside ton-client-entry.tsx only.
- Publish public/tonconnect-manifest.json ({ url, name, iconUrl }) and pass
manifestUrl={`${window.location.origin}/tonconnect-manifest.json`}.
- TESTNET IS FORCED: send with `network: CHAIN.TESTNET` and refuse to submit when
`wallet.account.chain !== CHAIN.TESTNET`, with a 'switch your wallet to testnet' hint.
- Amounts are nanotons as strings ("50000000" = 0.05 TON). A text-comment body is
beginCell().storeUint(0, 32).storeStringTail(text).endCell().toBoc().toString('base64').
- Read chain state from the public indexer https://testnet.toncenter.com/api/v3
(/transactions?account=…, /jetton/transfers?address=…). Never poll a wallet for history.
- Contracts live in /contracts, compiled with @ton-community/func-js and deployed with
@ton/ton + @ton/crypto from a node script — keep them out of the Vite bundle.
- Persistent amber "TON testnet only · no real value moves here" banner at the top of the shell.
PAYWALL PROTOCOL (HTTP 402 + memo verification)
- GET /api/public/ton-paid-content with no PAYMENT-MEMO header returns 402 and a challenge:
{ tonVersion: 1, network: "ton:testnet",
accepts: [{ scheme: "jetton-transfer", payTo: "<receiver>", asset: "<jetton master>",
amount: "<smallest units>", decimals: 6, symbol: "USDT",
memoPrefix: "chx-", maxTimeoutSeconds: 900 }] }
- The client mints a one-time memo (`chx-` + 12 hex chars), then sends ONE TON Connect message:
a TEP-74 `transfer` (op 0x0f8a7ea5) to the payer's jetton wallet with destination = payTo,
forward_ton_amount > 0 and a forward payload carrying the memo as a text comment.
Forward TON must be non-zero or the memo never reaches the receiver.
- The client then polls the same endpoint with the `PAYMENT-MEMO` header.
- The SERVER verifies independently — never trust the client's word for a payment:
GET https://testnet.toncenter.com/api/v3/jetton/transfers?address=<payTo>&direction=in&limit=50
match jetton_master === asset, amount >= required, decoded_forward_payload comment === memo,
transaction_aborted === false, and transaction_now within the last 15 minutes.
Only then return 200 with the payload plus a PAYMENT-RESPONSE header.
- Rate-limit the endpoint per IP and reject memos that do not match /^chx-[a-f0-9]{8,32}$/.
- Toncoin fallback: if no jetton master is configured, verify a plain 0.02 TON transfer with the
same memo through /transactions?account=<payTo> — identical flow, one config flag.
USER FLOW
1. Connect Tonkeeper (testnet) via TON Connect; free test coins from https://t.me/testgiver_ton_bot.
2. Painters hit the locked color-themed art auctions resource -> 402 challenge renders as a step list.
3. One wallet confirmation sends the Jetton transfer with the memo; the app polls until the
server sees it on-chain, then reveals the payload with a tonscan link.
4. Footer renders: "Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14"
TON SURVIVAL KIT (non-obvious rules — follow all of them)
1. BUFFER: browsers have no `Buffer`, but every TON lib returns one, so
`cell.toBoc().toString("base64")` throws "Buffer is not defined". Add one helper and
await it at the top of EVERY function touching @ton/core, @ton/crypto or @ton/ton —
including the TON Connect path, which is the one people forget:
export async function ensureBuffer() {
const g = globalThis as typeof globalThis & { Buffer?: unknown };
if (g.Buffer) return;
g.Buffer = (await import("buffer")).Buffer;
}
2. ADDRESS FLAVOURS: one key renders as four strings. `EQ…`/`UQ…` are the mainnet
flavours (bounceable / non-bounceable), `kQ…`/`0Q…` are the test-network flavours.
Never string-compare addresses across flavours — normalise first with
`Address.parse(a).equals(Address.parse(b))`. Render with an explicit flavour:
addr.toString({ testOnly: true, bounceable: false, urlSafe: true })
Send user transfers NON-bounceable (`bounce: false`); a bounceable send to an
uninitialised wallet returns the funds and the demo looks broken.
3. WALLET VERSION: the same mnemonic derives DIFFERENT addresses per wallet version.
A 24-word TON phrase -> `WalletContractV4` (workchain 0); a 12-word BIP39 phrase
(Gram, MyTonWallet) -> `WalletContractV5R1`. Derive both, query balances, and use
the funded one — do not guess. A TON wallet is itself a contract: it needs ~0.05 TON
and its first send carries `init` with `seqno: 0`. "Not deployed" is normal for a
fresh address, not an error.
4. EXPLORER: link everything to https://testnet.tonscan.org/address/<addr> and
https://testnet.tonscan.org/tx/<hash>. Derive the base URL from config, never hardcode.
5. WALLET REALITY CHECK: mobile Tonkeeper and Gram ship mainnet-only, so a test-network
demo opened on a phone dead-ends with "your wallet is on TON mainnet". Ship a fallback:
generate a throwaway wallet in the browser (`mnemonicNew()` from @ton/crypto, mnemonic
in localStorage, funded from the faucet) and sign locally with `WalletContractV4`.
Lock that browser-held key to the test network — never let it touch real funds.
6. TONCENTER: v3 rate-limits hard (HTTP 429). Wrap every read in a retry with backoff and
send `X-API-Key: TONCENTER_API_KEY` on anything user-facing.
7. PAYMENT VERIFICATION: verify SERVER-side only, never trust a client-reported hash.
Mint a one-time `chx-<hex>` memo, then match the recipient's recent transactions on
amount >= price, decoded comment === memo, `transaction_aborted === false`, and a
15-minute freshness window. Comments must be sent as a base64 BOC, not plain text:
beginCell().storeUint(0, 32).storeStringTail(memo).endCell().toBoc().toString("base64")
8. SSR: mount TON Connect client-side only — `lazy(() => import("./ton-client-entry"))`
inside `<ClientOnly>` + `<Suspense>`. Importing @tonconnect/ui-react at route module
scope crashes the server render. `tonconnect-manifest.json` must sit at a public https URL.
9. INSTALL: npm i @tonconnect/ui-react @ton/ton @ton/core @ton/crypto buffer
(add @ton-community/func-js only when you compile and deploy a FunC contract).
END TON SURVIVAL KIT
REQUIRED SECRETS (Lovable -> Project Settings -> Secrets):
- TON_DEPLOYER_MNEMONIC 24-word testnet wallet seed used by the deploy script. Fund it free: https://t.me/testgiver_ton_bot
- TONCENTER_API_KEY Optional but recommended for rate limits. Get one from https://t.me/tonapibot
- TON_RECEIVER_ADDRESS Testnet wallet that collects the Jetton payments (can equal the deployer).
- TON_JETTON_MASTER Jetton master address metered by the paywall; leave empty to meter in Toncoin.
- PINATA_JWT IPFS uploads (only if the app pins media). Docs: https://docs.pinata.cloud/llms-full.txt
CREDIT (must appear in the UI footer):
Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
Market sizing.
Indicative figures for hackathon pitches — refine with your own research before raising.