build strategy · onchain
Real onchain, two secrets, one build.
Every mega-prompt in this repo uses the same pattern, because it's the only pattern that lets a Lovable account ship a verifiable TON testnet demo in one shot.
Why TON testnet and not mainnet?
TON testnet is the real thing — the same TVM, the same tonscan explorer, the same Tonkeeper wallet in testnet mode — but funded by a free Telegram faucet. Every contract you deploy is publicly inspectable, you never spend real Toncoin, and your demo can't drain a user. Move to mainnet after the hackathon by swapping the endpoint and manifest.
The recipe
recipe
# 1. In your Lovable project, add the secrets (Settings -> Secrets): TON_DEPLOYER_MNEMONIC=word word word ... (24 words) PINATA_JWT=eyJhbGciOi... TONCENTER_API_KEY=... # optional, higher indexer rate limits TON_RECEIVER_ADDRESS=0Q... # only for Jetton paywall builds # 2. Fund the deployer wallet with free testnet TON: open https://t.me/testgiver_ton_bot # 3. Copy a mega-prompt from this repo into Lovable. One paste: # - scaffolds the React app # - writes the FunC contract (with hackathon credit in the header) # - compiles with @ton-community/func-js and deploys to TON testnet # - wires TON Connect (Tonkeeper / MyTonWallet / Tonhub) # - pins generated assets to IPFS via Pinata # - exposes the contract address + tonscan link in the UI # 4. Open the live tonscan link. Your demo is provably onchain.
1. The contract — credit baked in
Every FunC file deployed from a Creative Blockchain prompt MUST carry the hackathon credit in its header comment, so provenance lives onchain alongside the code.
contracts/provenance.fc
;; contracts/provenance.fc — every contract carries the hackathon credit in its header
;; Provenance
;; Built during the Creative AI & Quantum Hackathon
;; organised by StreetKode Fam during Indian Krump Festival 14
() 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);
}
2. Compile + deploy to TON testnet
scripts/deploy.ts
// scripts/deploy.ts — reads TON_DEPLOYER_MNEMONIC from process.env
import { compileFunc } from "@ton-community/func-js";
import { Cell, beginCell, contractAddress, internal, toNano } from "@ton/core";
import { TonClient, WalletContractV4 } from "@ton/ton";
import { mnemonicToPrivateKey } from "@ton/crypto";
const res = await compileFunc({ targets: ["provenance.fc"], sources });
const code = Cell.fromBoc(Buffer.from(res.codeBoc, "base64"))[0]!;
const data = beginCell().storeUint(0, 64).endCell();
const address = contractAddress(0, { code, data });
const key = await mnemonicToPrivateKey(process.env.TON_DEPLOYER_MNEMONIC!.split(" "));
const client = new TonClient({ endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC" });
const wallet = client.open(WalletContractV4.create({ workchain: 0, publicKey: key.publicKey }));
await wallet.sendTransfer({
seqno: await wallet.getSeqno(),
secretKey: key.secretKey,
messages: [internal({ to: address, value: toNano("0.05"), init: { code, data }, body: "deploy" })],
});
console.log("deployed:", address.toString({ testOnly: true }));
3. Pin assets to IPFS via Pinata
src/lib/pinata.ts
// src/lib/pinata.ts — pin a Blob to IPFS via Pinata JWT
export async function pinToIPFS(file: Blob, name = "artifact") {
const fd = new FormData();
fd.append("file", file, name);
const r = await fetch("https://api.pinata.cloud/pinning/pinFileToIPFS", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PINATA_JWT}` },
body: fd,
});
const { IpfsHash } = await r.json();
return IpfsHash as string; // the CID
}
4. Connect Tonkeeper via TON Connect
src/components/ton-client-entry.tsx
// src/components/ton-client-entry.tsx — TON Connect, client-only
import { TonConnectUIProvider, useTonConnectUI, CHAIN } from "@tonconnect/ui-react";
import { beginCell } from "@ton/core";
<TonConnectUIProvider manifestUrl={`${window.location.origin}/tonconnect-manifest.json`}>
<App />
</TonConnectUIProvider>;
// inside a component — one signed testnet message with a text comment
const [tonConnectUI] = useTonConnectUI();
const body = beginCell().storeUint(0, 32).storeStringTail(cid).endCell();
await tonConnectUI.sendTransaction({
network: CHAIN.TESTNET,
validUntil: Math.floor(Date.now() / 1000) + 300,
messages: [{ address: CONTRACT, amount: "50000000", payload: body.toBoc().toString("base64") }],
});
Hackathon rules of thumb
- · One mega-prompt = one build message. Don't iterate the architecture, iterate the UI.
- · Always show the live tonscan link in the UI — that's your proof.
- · Keep every send pinned to CHAIN.TESTNET and refuse to submit on mainnet.
- · Pin every user-generated asset to IPFS the moment it's created.
- · Add a "Built during the Creative AI & Quantum Hackathon — StreetKode Fam · Indian Krump Festival 14" line to your footer.