🎭 Theater & Live Performance · script collaboration

ScriptShare Vault

Facilitate secure, collaborative script revisions with onchain identity and no-fee transactions.

Privy Solana Login· auth
Section · Onchain

The primitive.

full primer →

Directors sign in with Google through Privy — no seed phrase, no wallet extension — and their script collaboration actions are signed by an embedded Solana wallet in the background.

Why this primitivePrivy social ensures verified users and sponsored tx enable frictionless edits.

Kernel
Privy signs users in with Google and provisions an embedded Solana wallet — no seed phrase, no Phantom extension required.
Drives the UI as
One-tap 'Continue with Google' drops users straight into the workspace with a funded Devnet address ready to sign program calls.
Appendix · Secrets

Required keys.

PRIVY_APP_ID
Google sign-in + embedded Solana wallet (Devnet).
open ↗
PINATA_JWT
Pin images / JSON / manifests to IPFS.
open ↗
LOVABLE_API_KEY
AI calls via the Lovable AI Gateway.
open ↗
SOLANA_RPC_URL
Alchemy Solana Devnet RPC (https://solana-devnet.g.alchemy.com/v2/<KEY>). The public api.devnet.solana.com is rate-limited and CORS-blocked in the browser.
open ↗
PHANTOM_PRIVATE_KEY
Base58 secret key exported from Phantom (Settings → Export Private Key). Used ONCE by scripts/deploy.ts to pay rent + fees for your bespoke program on Devnet. Fund it from https://faucet.solana.com. Never shipped to the browser.
open ↗

Add these in your Lovable project under Settings → Secrets before pasting the prompt below.

Appendix · Mega-prompt

The build prompt.

Paste into a fresh Lovable project. Make sure all five secrets above are set first. read the build strategy →

Build "ScriptShare Vault" in ONE Lovable message. Single-page demo on Solana Devnet. This prompt
is self-contained — do NOT rely on any external Solana skill or docs beyond what is inlined here.

CONCEPT
Facilitate secure, collaborative script revisions with onchain identity and no-fee transactions.
Discipline: Theater & Live Performance (script collaboration).
Onchain primitive: Privy Solana Login. Why this primitive: Privy social ensures verified users and sponsored tx enable frictionless edits.

5-CREDIT BUDGET (HARD LIMIT — one Lovable message, no follow-ups):
- ONE single-page app (index route only). No router, no Lovable Cloud, no database, no auth flows
  beyond the Privy drop-in.
- ONE bespoke Solana program deployed to Devnet FROM THE SANDBOX during this build (see DEPLOY),
  then called from the browser via Privy's embedded wallet. No Anchor framework — plain
  `solana-program` crate keeps the .so under 100 KB and rent under 0.5 SOL.
- Privy is ALWAYS the browser auth + signing layer (Google login, embedded Solana wallet on Devnet).
- Pinata/IPFS only if the idea genuinely needs to store a file or JSON metadata.
- At most ONE `LOVABLE_API_KEY` call per user action, invoked from a server function (never the browser).
- Skip tests, skip CI, skip docs pages. Ship the demo, nothing else.

REQUIRED SECRETS (Lovable -> Project Settings -> Secrets — add BEFORE sending this prompt):
- LOVABLE_API_KEY         Auto-provisioned by Lovable. One AI call per user action, max.
- PRIVY_APP_ID            From https://dashboard.privy.io. In the SAME Privy app, whitelist BOTH
                          `https://*.lovable.app` AND your published `https://<slug>.lovable.app`
                          under Allowed Origins, or the signing iframe is killed by `frame-ancestors`
                          CSP and every tx fails with a blank "Something went wrong" modal.
                          Enable "Solana gas sponsorship" for cluster `solana:devnet` in the same
                          dashboard so users never see a fund-wallet prompt.
- PINATA_JWT              Only required if the app pins media/metadata to IPFS.
                          Get one at https://app.pinata.cloud (JWT, not the legacy API key pair).
- SOLANA_RPC_URL          Alchemy Solana Devnet RPC: https://solana-devnet.g.alchemy.com/v2/<KEY>.
                          The public https://api.devnet.solana.com is rate-limited and blocks
                          browser CORS on getLatestBlockhash — do NOT use it.
- PHANTOM_PRIVATE_KEY     Base58 secret key exported from YOUR Phantom wallet (Settings -> Security
                          & Privacy -> Show Secret Recovery Phrase -> Export Private Key). Fund it
                          with ~2 Devnet SOL from https://faucet.solana.com BEFORE sending this
                          prompt. Lovable uses this key EXACTLY ONCE inside the sandbox during this
                          build to `solana program deploy` the bespoke program, then wipes it. The
                          browser bundle must never import it.

STACK
- React + Vite single page (the index route only).
- `@solana/web3.js` for tx building. `@privy-io/react-auth` v3+ for auth AND signing.
- Cluster: Devnet ONLY. Never mainnet. Use the Alchemy `SOLANA_RPC_URL` everywhere — in the
  frontend Connection, in `PrivyProvider.solana.rpcs`, and in the sandbox `solana config`.
- Devnet gas is sponsored by Privy (dashboard setting). Never call `requestAirdrop`, never render
  a "fund wallet" UI.

VITE + BUFFER POLYFILL (4 layers — omit any one and it works in dev but blanks the
published mobile build):
1. `bun add buffer vite-plugin-node-polyfills`.
2. `vite.config.ts`:
```ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { nodePolyfills } from "vite-plugin-node-polyfills";
export default defineConfig({
  plugins: [react(), nodePolyfills({ globals: { Buffer: true, global: true }, protocolImports: true })],
  optimizeDeps: { esbuildOptions: { define: { global: "globalThis" } } },
  resolve: { alias: { "rpc-websockets/dist/lib/client": "rpc-websockets/dist/lib/client.browser.js" } },
});
```
DO NOT add a top-level `define: { global: "globalThis" }` in vite.config — it conflicts with the
plugin's Rollup inject and leaves Buffer undefined in dependency chunks. DO NOT put `import`
statements inside `build.rollupOptions.output.intro` — top-level import inside a chunk wrapper is
illegal and blanks the page.
3. `index.html` — inline BEFORE any bundle loads:
```html
<script type="module">
  import { Buffer } from "https://esm.sh/buffer@6";
  globalThis.Buffer = globalThis.Buffer || Buffer;
</script>
```
4. `src/polyfills.ts` (imported FIRST in `src/main.tsx`, before anything else):
```ts
import { Buffer } from "buffer";
(globalThis as any).Buffer = (globalThis as any).Buffer || Buffer;
(window as any).Buffer = (window as any).Buffer || Buffer;
```
Then in every file that touches Solana, add `import { Buffer } from "buffer";` at the top — the
plugin cannot inject into your own TypeScript source.

PRIVY (browser auth + Solana signing) — Privy v3, MUST include `solana.rpcs`:
Install: `bun add @privy-io/react-auth @solana/web3.js @solana/kit @solana-program/memo`
(`@solana-program/memo` is an implicit peer dep of `@privy-io/react-auth/solana`; missing it
crashes the Privy modal at runtime).

`src/components/PrivyRoot.tsx` (single-page app, no SSR concerns here, but keep imports scoped):
```tsx
import { PrivyProvider } from "@privy-io/react-auth";
import { toSolanaWalletConnectors } from "@privy-io/react-auth/solana";
import { createSolanaRpc, createSolanaRpcSubscriptions } from "@solana/kit";

const rpcUrl = import.meta.env.VITE_SOLANA_RPC_URL!;         // set from SOLANA_RPC_URL secret
const wsUrl  = rpcUrl.replace("https://", "wss://");

export function PrivyRoot({ children }: { children: React.ReactNode }) {
  return (
    <PrivyProvider
      appId={import.meta.env.VITE_PRIVY_APP_ID!}
      config={{
        loginMethods: ["google", "email"],
        embeddedWallets: { solana: { createOnLogin: "users-without-wallets" } },
        externalWallets: { solana: { connectors: toSolanaWalletConnectors() } },
        solanaClusters: [{ name: "devnet", rpcUrl }],
        solana: {
          rpcs: {
            "solana:devnet": {
              rpc: createSolanaRpc(rpcUrl),
              rpcSubscriptions: createSolanaRpcSubscriptions(wsUrl),
            },
          },
        },
      }}
    >{children}</PrivyProvider>
  );
}
```
Without the `solana.rpcs` mapping, Privy throws "No RPC configuration found for chain solana:devnet"
the moment the user tries to sign.

Expose the secrets to the browser in `.env.local` (Lovable already mirrors non-VITE secrets, but
Vite only ships `VITE_*` to the client):
```
VITE_PRIVY_APP_ID=<same value as PRIVY_APP_ID secret>
VITE_SOLANA_RPC_URL=<same value as SOLANA_RPC_URL secret>
```

Sign + send with sponsorship:
```tsx
import { useSolanaWallets, useSignAndSendTransaction } from "@privy-io/react-auth/solana";
const { wallets } = useSolanaWallets();
const embedded = wallets.find(w => w.walletClientType === "privy");
const { signAndSendTransaction } = useSignAndSendTransaction();
const { signature } = await signAndSendTransaction(tx, {
  address: embedded!.address,           // pin to embedded wallet — else Privy routes to an external one
  sponsor: true,                        // opts into the dashboard's gas sponsorship
});
```
Never call `connection.requestAirdrop` and never render a "fund your wallet" UI — sponsorship
covers everything on Devnet.

DEPLOY (Lovable runs this ONCE, inside the sandbox, during THIS build message —
it counts toward the 5-credit budget, so make it the first thing you do):

1. Create `solana/program/src/lib.rs` — a tiny native Rust program (no Anchor) with on-chain
security metadata so Explorer's Security tab shows contact + source URL even before OtterSec
verification lands:
```rust
use solana_program::{account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
    msg, pubkey::Pubkey};
#[cfg(not(feature = "no-entrypoint"))]
use solana_security_txt::security_txt;

#[cfg(not(feature = "no-entrypoint"))]
security_txt! {
    name: "<your-program-name>",
    project_url: "https://<your-slug>.lovable.app",
    contacts: "email:you@example.com",
    policy: "https://github.com/<user>/<repo>/blob/main/SECURITY.md",
    source_code: "https://github.com/<user>/<repo>",
    preferred_languages: "en",
    auditors: "None"
}

entrypoint!(process);
pub fn process(_pid: &Pubkey, _accs: &[AccountInfo], data: &[u8]) -> ProgramResult {
    msg!("payload: {}", std::str::from_utf8(data).unwrap_or("<binary>"));
    Ok(())
}
```
and `solana/program/Cargo.toml` with `[lib] crate-type = ["cdylib", "lib"]`,
`[features] no-entrypoint = []`, and dependencies `solana-program = "1.18"`,
`solana-security-txt = "1.1"`.

Also create `solana/program/rust-toolchain.toml` pinning the channel — this is REQUIRED for
`solana-verify --remote` to produce the same hash as on-chain:
```toml
[toolchain]
channel = "1.89.0"
```

2. In the sandbox shell (run in this exact order):
```bash
# install toolchains if missing
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
bun add bs58

# materialise the deployer keypair from the Phantom secret
node -e '
  const bs58 = require("bs58");
  const fs = require("fs");
  const secret = bs58.decode(process.env.PHANTOM_PRIVATE_KEY);
  fs.writeFileSync("/tmp/deployer.json", JSON.stringify(Array.from(secret)));
'
solana config set --url "$SOLANA_RPC_URL" --keypair /tmp/deployer.json
solana balance                                    # sanity check — must be >= 2 SOL

# build + deploy (commit Cargo.lock afterwards — verification needs it)
cargo build-sbf --manifest-path solana/program/Cargo.toml
PROGRAM_ID=$(solana program deploy \
  solana/program/target/deploy/*.so --output json | jq -r .programId)
echo "export const PROGRAM_ID = \"$PROGRAM_ID\";" > src/lib/program-id.ts

# wipe the deployer key — the browser bundle must never see it
rm -f /tmp/deployer.json
```

3. Frontend imports `PROGRAM_ID` from `src/lib/program-id.ts`. The Program ID is the only Solana
program identifier that ever reaches the browser bundle.

VERIFY (reproducible build badge on Solana Explorer) — do NOT attempt submission
from the sandbox; instead ship the artifacts so the user can run it themselves once their repo is
public on GitHub:

1. Commit `solana/program/rust-toolchain.toml` AND `solana/program/Cargo.lock` — OtterSec's remote
builder needs both to reproduce the exact on-chain hash. A missing lockfile or unpinned toolchain
returns "hash mismatch" every time.

2. Create `SECURITY.md` at the repo root with a contact email and this "How to verify" section.

3. Create `solana/program/VERIFY.md` with the exact command, program ID substituted in:
```
solana-verify verify-from-repo --remote \
  --url https://api.devnet.solana.com \
  --program-id <PROGRAM_ID> \
  --library-name <snake_case_crate_name> \
  --mount-path solana/program \
  --keypair ~/.config/solana/id.json \
  https://github.com/<user>/<repo>
```

4. Add `src/components/verification-card.tsx` that polls `https://verify.osec.io/status/<PROGRAM_ID>`
every 30s and renders "Reproducible build ✓ verified" (emerald) when `is_verified` is true,
"Verification pending" (amber) otherwise. Render it on the same page as the program demo.

Hard requirements the user must know about (put them in `VERIFY.md`):
- Repo MUST be **public** on GitHub. OtterSec clones anonymously; private repos fail with a clone
  error and Explorer will never show the green badge.
- The `~/.config/solana/id.json` on the user's machine MUST be the same upgrade authority that
  deployed the program (i.e. the Phantom key they used as `PHANTOM_PRIVATE_KEY`).
- Verification is Docker-free thanks to `--remote` (OtterSec builds server-side). The user does
  NOT need Docker installed.

CALL THE DEPLOYED PROGRAM (`src/lib/tx.ts`) — order of `keys` MUST match
your Rust `#[derive(Accounts)]` field-for-field (a swap surfaces as `AccountNotInitialized` or a bare
signature-verification error with no useful message):
```ts
import { Buffer } from "buffer";
import { Connection, PublicKey, Transaction, TransactionInstruction } from "@solana/web3.js";
import { PROGRAM_ID } from "./program-id";

export function connection() {
  return new Connection(import.meta.env.VITE_SOLANA_RPC_URL!, "confirmed");
}

export async function buildTx(payer: PublicKey, payload: string) {
  const ix = new TransactionInstruction({
    keys: [{ pubkey: payer, isSigner: true, isWritable: true }],  // order is load-bearing
    programId: new PublicKey(PROGRAM_ID),
    data: Buffer.from(payload, "utf8"),
  });
  const { blockhash } = await connection().getLatestBlockhash();
  const tx = new Transaction({ recentBlockhash: blockhash, feePayer: payer }).add(ix);
  return tx;
}
```
Do NOT install `@coral-xyz/anchor` — it drags in Node-only deps that fight Vite. Build instructions
by hand as above. Explorer link format: `https://explorer.solana.com/tx/<sig>?cluster=devnet`.

USER FLOW
1. Land on page -> "Continue with Google" (Privy) -> embedded Solana wallet auto-provisioned,
   Devnet gas sponsored — no airdrop or funding UI.
2. Every script collaboration action the user performs is turned into a bespoke-program tx signed by their Privy embedded wallet (zero clicks after Google login, gas sponsored) and displayed with a Solana Explorer link.
3. Footer renders the credit line below.

COMMON GOTCHAS (each one has bitten prior participants — read before shipping):
- "Buffer is not defined" on published mobile only: you skipped one of the 4 polyfill layers above.
- Privy modal shows "Something went wrong" and console has a `frame-ancestors` CSP error:
  add your `*.lovable.app` origin under Privy Dashboard -> Allowed Origins for the SAME app whose
  ID is in `PRIVY_APP_ID`.
- Privy modal shows "No RPC configuration found for chain solana:devnet": you forgot the
  `solana.rpcs` mapping in `PrivyProvider` config.
- `signAndSendTransaction` succeeds but user gets a wallet-connect prompt: pass
  `{ address: embedded.address, sponsor: true }` — omitting `address` lets Privy route to an
  external wallet.
- `429 Too Many Requests` / CORS errors from `api.devnet.solana.com`: switch `SOLANA_RPC_URL` to
  Alchemy Devnet.
- `cargo build-sbf: command not found`: install the Anza CLI (see DEPLOY step 2).
- Program deploy fails with "insufficient funds for rent": the wallet backing
  `PHANTOM_PRIVATE_KEY` isn't funded — hit https://faucet.solana.com and re-run just the deploy step.
- OtterSec verification returns "hash mismatch": `rust-toolchain.toml` or `Cargo.lock` isn't
  committed, or the repo isn't public. Fix all three and re-run the `solana-verify` command.

CREDIT (must appear in UI footer AND, if you mint an NFT, inside the pinned metadata `description` field):
Built during the Creative AI & Quantum Hackathon organised by StreetKode Fam during Indian Krump Festival 14
Appendix · Market

Market sizing.

TAM
$500M
global playwriting markets
SAM
$120M
digital script collaboration tools
SOM
$25M
theater groups using blockchain collaboration

Indicative figures for hackathon pitches — refine with your own research before raising.

See also

Adjacent entries.