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.