CollaboChain Studio
Share locked versions of photos and edits on IPFS for trustable collaborative workflows.
IPFS via Pinata· decentralized storage
Section · Onchain
full primer →The primitive.
Every collaborative editing artefact is pinned to IPFS through Pinata; photographers get a permanent CID and a public gateway preview instead of a fragile cloud URL.
Why this primitivePinning edits and base images via JWT creates permanent, shared CID histories.
Kernel
a Pinata JWT upload that pins images / JSON / manifests to IPFS and returns a permanent CID
Drives the UI as
a 'pinned to IPFS' chip with the CID and an ipfs.io gateway preview
Required keys.
TON_DEPLOYER_MNEMONIC
24-word testnet seed for the deploy script. Fund it free via the Telegram test giver.
open ↗Add these in your Lovable project under Settings → Secrets before pasting the prompt below.
Appendix · Mega-prompt
The build prompt.
budget · 1 message
Target
Paste into a fresh Lovable project. Make sure all five secrets above are set first. read the build strategy →
Build "CollaboChain Studio" in ONE Lovable message. Single-page demo on the TON testnet.
CONCEPT
Share locked versions of photos and edits on IPFS for trustable collaborative workflows.
Discipline: Photography (collaborative editing).
Onchain primitive: IPFS via Pinata. Why this primitive: Pinning edits and base images via JWT creates permanent, shared CID histories.
5-CREDIT BUDGET (HARD LIMIT)
- ONE single-page app. No router, no Lovable Cloud, no database, no auth beyond TON Connect.
- ONE FunC contract, <=60 lines, deployed to TON testnet and readable on tonscan.
- TON Connect is always the wallet layer (Tonkeeper, MyTonWallet, Tonhub).
- Pinata/IPFS only if the idea genuinely needs to store a file or metadata.
- At most ONE AI call per user action (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 + 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.
PINNING
- One server function uploads the artefact to Pinata with PINATA_JWT and returns the CID.
- The CID is then anchored on TON: send it as a text-comment message to the CollaboChainStudio contract
(see CONTRACT below) so the pin has an on-chain timestamp, not just a gateway URL.
CONTRACT (contracts/CollaboChainStudio.fc — FunC, compiled with @ton-community/func-js):
```func
;; CollaboChainStudio — Share locked versions of photos and edits on IPFS for trustable collaborative workflows.
;; Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
slice begin_parse(cell c) asm "CTOS";
builder begin_cell() asm "NEWC";
cell end_cell(builder b) asm "ENDC";
cell get_data() asm "c4 PUSH";
() set_data(cell c) impure asm "c4 POP";
int slice_bits(slice s) asm "SBITS";
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
if (in_msg_body.slice_bits() < 32) { return (); }
int op = in_msg_body~load_uint(32);
if (op != 0) { return (); } ;; text-comment messages only
slice ds = get_data().begin_parse();
int total = ds.slice_bits() >= 64 ? ds~load_uint(64) : 0;
set_data(begin_cell().store_uint(total + 1, 64).end_cell());
}
int total_logs() method_id {
slice ds = get_data().begin_parse();
if (ds.slice_bits() < 64) { return 0; }
return ds~load_uint(64);
}
```
The payload itself lives forever in the transaction history; the counter proves the
contract actually processed it. Deploy with a node script that builds
`contractAddress(0, { code, data: beginCell().storeUint(0, 64).endCell() })`, sends 0.05 TON
with `init`, then writes the address into src/data/ton.json.
USER FLOW
1. Connect Tonkeeper via TON Connect (testnet).
2. Photographers upload a collaborative editing artefact -> pinned to IPFS -> CID shown with an ipfs.io preview.
3. One tap anchors the CID on TON testnet; the row links to both the gateway and tonscan.
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
- 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 AND as a comment header in every deployed contract):
Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
Market sizing.
TAM
$2.4B
global photo software market
SAM
$300M
collaborative photo editing tools
SOM
$40M
decentralized collaborative editors
Indicative figures for hackathon pitches — refine with your own research before raising.
Adjacent entries.
collaborative editing
Collaborative Edits Chain
Track photo edit histories and collaborators immutably on TON testnet for transparent teamwork.
photo archivingChainFrame Archive
Securely store and verify photo archives with immutable IPFS-backed metadata for trusted provenance.
photojournalism documentationPinPhoto Journal
Verify and timestamp news photos on IPFS to ensure authentic, unaltered media for journalism.
color gradingColorChain Palette
Save and share color grading presets and images as permanent IPFS assets for consistent workflow.