build strategy · sui testnet

Real on Sui, three 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 Sui Testnet demo in one shot.

Why Sui Testnet and not mainnet?

Sui Testnet runs the same Move VM, the same Suiscan explorer, and the same wallets as mainnet — but funded by a free faucet. Every Move package you publish is publicly inspectable, but you never spend real SUI and your demo can't accidentally drain a user. Users sign in with Google (Lovable-managed OAuth) and get a server-derived custodial Sui address they fund via the faucet — no wallet extension required. Move to mainnet after the hackathon by switching the network in the SuiClient.

The recipe

recipe
# 1. In your Lovable project, add two secrets (Settings -> Secrets):
SUI_DEPLOYER_KEY=...     # base64 Sui keypair, funded on Testnet
SUI_WALLET_SALT=...      # 64-char random, generated once in Lovable Cloud secrets
# Optional (only if the idea pins media):
WALRUS_PUBLISHER_URL=...

# 2. Fund the deployer address on Sui Testnet:
open https://faucet.testnet.sui.io

# 3. Copy a mega-prompt from this repo into Lovable. One paste:
#    - scaffolds the React app + TanStack Start server functions
#    - writes the Move module (with hackathon credit in doc-comments)
#    - publishes to Sui Testnet, writes packageId to src/data/sui-package.json
#    - wires Google sign-in via Lovable (managed OAuth, no console setup)
#    - derives a per-user custodial Sui wallet server-side via HKDF
#    - uploads artefacts to Walrus and stores the blobId on-chain
#    - exposes the packageId + Suiscan link in the UI

# 4. Open the live Suiscan link. Your demo is provably on Sui.

1. The Move module — credit baked in

Every Move file published from a Creative Blockchain prompt MUST carry the hackathon credit as a doc-comment, so provenance lives on-chain alongside the bytecode.

move/sources/provenance.move
/// move/sources/provenance.move — every module carries the hackathon credit as a doc-comment
/// Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14.
module provenance::provenance {
    use sui::event;
    use sui::tx_context::{Self, TxContext};

    public struct Logged has copy, drop {
        author: address,
        blob_id: vector<u8>,
        at_ms: u64,
    }

    /// Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14.
    entry public fun log(blob_id: vector<u8>, ctx: &mut TxContext) {
        event::emit(Logged {
            author: tx_context::sender(ctx),
            blob_id,
            at_ms: tx_context::epoch_timestamp_ms(ctx),
        });
    }
}

2. Publish to Sui Testnet

Sui doesn't have Etherscan-style source verification. Instead, publish your Move source alongside the packageId and link to suiscan.xyz/testnet/object/<packageId>for on-chain inspection.

scripts/publish.sh
# scripts/publish.sh — publish the Move package to Sui Testnet
# Requires SUI_DEPLOYER_KEY funded via https://faucet.testnet.sui.io
set -euo pipefail

sui client switch --env testnet
cd move
sui client publish --gas-budget 100000000 --json > ../publish.json

# Extract the packageId and persist it for the frontend
node -e "
  const p = require('./publish.json');
  const pkg = p.objectChanges.find(c => c.type === 'published');
  require('fs').writeFileSync(
    'src/data/sui-package.json',
    JSON.stringify({
      packageId: pkg.packageId,
      network: 'testnet',
      module: 'provenance',
      eventType: pkg.packageId + '::provenance::Logged',
      publishedAt: new Date().toISOString(),
      explorer: 'https://suiscan.xyz/testnet'
    }, null, 2)
  );
"

3. Store artefacts on Walrus

src/lib/walrus.ts
// src/lib/walrus.ts — upload a Blob to Walrus, get back a permanent blobId
const PUBLISHER = "https://publisher.walrus-testnet.walrus.space";
const AGGREGATOR = "https://aggregator.walrus-testnet.walrus.space";

export async function uploadToWalrus(file: Blob, epochs = 5) {
  const r = await fetch(`${PUBLISHER}/v1/store?epochs=${epochs}`, {
    method: "PUT",
    body: file,
  });
  const json = await r.json();
  const blobId = json.newlyCreated?.blobObject?.blobId ?? json.alreadyCertified?.blobId;
  return { blobId, url: `${AGGREGATOR}/v1/${blobId}` };
}

4. Sign in with Google (Lovable-managed) + custodial Sui wallet

Google OAuth is managed by Lovable — no Google Cloud console setup. On sign-in, the server derives a per-user Ed25519 Sui keypair from SUI_WALLET_SALT + the verified user id via HKDF, then signs transactions inside a createServerFn. Private keys never leave the server. Custodial demo pattern — Testnet only.

sign-in + sui-wallet.server.ts
// 1. Client sign-in — src/components/sign-in-button.tsx
import { lovable } from "@/integrations/lovable";

export function SignInButton() {
  return (
    <button
      onClick={() =>
        lovable.auth.signInWithOAuth("google", {
          redirect_uri: window.location.origin,
        })
      }
    >
      Sign in with Google
    </button>
  );
}

// 2. Server-derived custodial wallet — src/lib/sui-wallet.server.ts
import { hkdfSync } from "node:crypto";
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";

export function deriveKeypair(userId: string) {
  const salt = Buffer.from(process.env.SUI_WALLET_SALT!, "utf8");
  const ikm = Buffer.from(userId, "utf8");
  const secret = hkdfSync("sha256", ikm, salt, Buffer.from("sui/ed25519"), 32);
  return Ed25519Keypair.fromSecretKey(new Uint8Array(secret));
}

// Use inside a createServerFn().middleware([requireSupabaseAuth]).handler(...)
// so signing only ever runs server-side with the caller's verified user id.

Hackathon rules of thumb

  • · One mega-prompt = one build message. Don't iterate the architecture, iterate the UI.
  • · Always show the live Suiscan link in the UI — that's your proof.
  • · Use Lovable's managed Google OAuth and a server-derived custodial wallet so judges don't need a wallet extension.
  • · Upload every user-generated artefact to Walrus the moment it's created and store the blobId on-chain.
  • · Add a "Built during the Creative AI & Quantum Hackathon — StreetKode Fam · Indian Krump Festival 14" line to your footer.